C 字符串和字符数组的内存分配
我无法理解以下代码中如何分配内存:C 字符串和字符数组的内存分配,c,string,memory-management,C,String,Memory Management,我无法理解以下代码中如何分配内存: #include<stdio.h> #include<string.h> int main() { char a[]={"text"}; char b[]={'t','e','x','t'}; printf(":%s: sizeof(a)=%d, strlen(a)=%d\n",a, sizeof(a), strlen(a)); printf(":%s: sizeof(b)=%d, strlen(b)=
#include<stdio.h>
#include<string.h>
int main()
{
char a[]={"text"};
char b[]={'t','e','x','t'};
printf(":%s: sizeof(a)=%d, strlen(a)=%d\n",a, sizeof(a), strlen(a));
printf(":%s: sizeof(b)=%d, strlen(b)=%d\n",b, sizeof(b), strlen(b));
return 0;
}
通过查看内存地址和输出代码,变量b似乎放在变量a之前,这就是strlen(b)通过查找\0返回8的原因。
为什么会发生这种情况?我希望先声明变量a。您的“b”字符数组不是以null结尾的。要理解,CARAR A[]声明相当于:
char a[] = { 't', 'e', 'x', 't', '\0' };
换句话说,strlen(b)未定义,它只是在随机内存中查找空字符(0字节)。我没有得到相同的输出,请参见我的ideone代码段: 当我使用codepad时,我看到的输出与您不同:
也可以,编译C++编译器时,得到相同的输出:
因此,您的平台和/或编译器肯定出了问题。它可能是未定义的行为(UB),因为您的字符数组没有空终止符(\0)。无论如何 虽然a和b看起来可能相同,但这并不是因为您定义了字符数组char a[] = "text";
此阵列在内存中的外观如下所示:
----------------------
| t | e | x | t | \0 |
----------------------
双引号表示“文本字符串”,将自动添加\0(这就是为什么大小为5)。在b中,您必须手动添加,但大小为4。b中的
strlen()。对于非空终止字符数组的编码,这在许多安全方面都是一个大问题。该语言不保证将什么放在哪里。所以,你的实验毫无意义。这可能有效,也可能无效。该行为未定义。您的b
不是字符串,将strlen
与非字符串的内容一起使用是非常必要的
然而,从纯粹实用的角度来看,局部变量通常在堆栈上分配,而may moderns平台(如x86)上的堆栈向后增长,即从较高的地址到较低的地址。因此,如果您使用的是这些平台中的一个,那么您的编译器可能决定按照变量声明的顺序分配变量(a
第一个和b
第二个),但因为堆栈向后增长b
在内存中的地址比a
低。即b
在内存中的a
之前结束
但是可以注意到,典型的实现通常不会逐个为局部变量分配堆栈空间。相反,所有局部变量(堆栈帧)的整个内存块一次分配,这意味着我上面描述的逻辑不一定适用。然而,仍然有可能编译器仍然遵循局部变量布局的“反向”方法,即先前声明的变量稍后放置在局部内存帧中,“好像”它们是按照声明的顺序一个接一个地分配的。我在Linux/x86上用GCC编译代码,使用-S标志查看程序集输出。这表明对我来说,b[]分配的内存地址比a[]高,所以我没有得到strlen(b)=4
在上面的代码中,$1954047348后跟$0是一个以null结尾的[]。之后的4个字节是b[]。这意味着,由于堆栈在此编译器上向下生长,因此在堆栈上b[]被推到a[]之前
如果使用-S(或等效项)进行编译,则应在比a[]更低的地址处看到b[],因此将得到strlen(b)=8。不保证按任何特定顺序分配内存<代码>a
可以在b
之前或之后出现,也可以彼此不在一起。这是事实。这就是为什么它在堆栈中更高。但是你真的不应该依赖于这样的期望。将strlen()与字符数组一起使用可能是危险的。@只是另一个程序员:没有比在分配的内存上调用strlen
更危险的了。在我看来,这个问题清楚地表明OP知道这一点。问题真的是“为什么a高于b?”@Pascal:这是他的编译器或其他东西,请看我的答案。您在这里多次说“正确的输出”,但肯定strlen(b)
is UB?@0A0D:未定义的行为。你后来(在底部)含糊地提到了它。我想我的观点是,从技术上讲,对于strlen(b)@Frexus,任何答案都可能是“正确的”:你没有说明你是如何推断b是在a之前声明的,除了你有一个加倍的单词text。这确实很奇怪,因为我展示的三种不同的编译器都没有显示这种行为。既然“字符串”不是以null结尾的,任何事情都可能发生。@0A0D,为什么说平台或编译器有问题?OP导致了未定义的行为——这当然不是他的工具的错。错,错,错。他们的编译器没有问题-在确定什么是正确的行为或其他行为时,您必须根据标准而不是编译器工作。如果你从一个错误的前提开始,世界上所有的演绎推理都不会帮助你。
:text: sizeof(a)=5, strlen(a)=4
:text: sizeof(b)=4, strlen(b)=4
:text: sizeof(a)=5, strlen(a)=4
:text: sizeof(b)=4, strlen(b)=4
char a[] = "text";
----------------------
| t | e | x | t | \0 |
----------------------
.file "str.c"
.section .rodata
.align 4
.LC0:
.string ":%s: sizeof(a)=%d, strlen(a)=%d\n"
.align 4
.LC1:
.string ":%s: sizeof(b)=%d, strlen(b)=%d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
movl %gs:20, %eax
movl %eax, 28(%esp)
xorl %eax, %eax
movl $1954047348, 19(%esp)
movb $0, 23(%esp)
movb $116, 24(%esp)
movb $101, 25(%esp)
movb $120, 26(%esp)
movb $116, 27(%esp)
leal 19(%esp), %eax
movl %eax, (%esp)
call strlen
movl %eax, %edx
movl $.LC0, %eax
movl %edx, 12(%esp)
movl $5, 8(%esp)
leal 19(%esp), %edx
movl %edx, 4(%esp)
movl %eax, (%esp)
call printf
leal 24(%esp), %eax
movl %eax, (%esp)
call strlen
movl $.LC1, %edx
movl %eax, 12(%esp)
movl $4, 8(%esp)
leal 24(%esp), %eax
movl %eax, 4(%esp)
movl %edx, (%esp)
call printf
movl $0, %eax
movl 28(%esp), %edx
xorl %gs:20, %edx
je .L2
call __stack_chk_fail
.L2:
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
.section .note.GNU-stack,"",@progbits