int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);
导致段错误,而
int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);
没有按'吨。 现在:
int *nums = {5, 2, 1, 4};
printf("%d\n", nums);
打印5。
基于此,我猜测数组初始化符号{}会盲目地将这些数据加载到左边的任何变量中。当它是int []时,数组将根据需要填充。当它是int *时,指针被填满5,并且存储指针之后的存储器位置被填充为2,1和4.因此nums [0]尝试deref 5,导致段错误。 / p>
如果我错了,请纠正我。如果我是正确的,请详细说明,因为我不明白为什么数组初始化程序的工作方式如何。
答案 0 :(得分:113)
C中有一条(愚蠢的)规则说任何普通变量都可以用括号括起的初始化列表初始化,就好像它是一个数组一样。
例如,您可以编写int x = {0};
,这完全等同于int x = 0;
。
因此,当您编写int *nums = {5, 2, 1, 4};
时,您实际上正在为单个指针变量提供初始化列表。但是,它只是一个变量,因此它只会被赋予第一个值5,列表的其余部分将被忽略(实际上我并不认为具有多余初始化程序的代码甚至应该使用严格的编译器进行编译) - 它根本没有写入记忆。代码等同于int *nums = 5;
。这意味着,nums
应指向地址 5
。
此时您应该已经收到两个编译器警告/错误:
当然,代码会崩溃并烧毁,因为5
很可能不是有效地址,您可以使用nums[0]
取消引用。
作为旁注,您应该使用printf
说明符%p
指针地址,否则您将调用未定义的行为。
我不太确定你要在这里做什么,但是如果你想设置一个指向数组的指针,你应该这样做:
int nums[] = {5, 2, 1, 4};
int* ptr = nums;
// or equivalent:
int* ptr = (int[]){5, 2, 1, 4};
或者如果你想创建一个指针数组:
int* ptr[] = { /* whatever makes sense here */ };
修改强>
经过一些研究后我可以说"多余的元素初始化列表"确实无效C - 它是GCC extension。
标准 6.7.9初始化表示(强调我的):
2 没有初始值设定项应尝试为对象提供值 包含在正在初始化的实体中。
/ - /
11 标量的初始值设定项应为单个表达式, 可选地用大括号括起来。对象的初始值是 表达式(转换后);相同的类型约束和 适用于简单分配的转换,采用类型 标量是其声明类型的非限定版本。
"标量类型"是一个标准术语,指的是不是数组,结构或联合类型的单个变量(那些被称为"聚合类型")。
所以用简单的英语标准说:"当你初始化一个变量时,可以随意在初始化表达式周围抛出一些额外的括号,只因为你可以。"
答案 1 :(得分:28)
情景1
int *nums = {5, 2, 1, 4}; // <-- assign multiple values to a pointer variable printf("%d\n", nums[0]); // segfault
为什么这一段错误?
您将nums
声明为指向int的指针 - nums
应该在内存中保存一个整数的地址。
然后,您尝试将nums
初始化为多个值的数组。因此,如果不深入研究很多细节,那么在概念上是不正确的 - 将多个值分配给应该包含一个值的变量是没有意义的。在这方面,如果你这样做,你会看到完全相同的效果:
int nums = {5, 2, 1, 4}; // <-- assign multiple values to an int variable
printf("%d\n", nums); // also print 5
在任何一种情况下(为指针或int变量分配多个值),接下来会发生变量将获得第一个值5
,而忽略其余值。此代码符合但您会收到不应该在分配中的每个附加值的警告:
warning: excess elements in scalar initializer
。
对于为指针变量分配多个值的情况,当您访问nums[0]
时,程序会出现段错误,这意味着您将从字面上引用存储在地址5 中的任何内容。在这种情况下,您没有为指针nums
分配任何有效内存。
值得注意的是,在为int变量分配多个值的情况下没有段错误(这里没有解除引用任何无效指针)。
情景2
int nums[] = {5, 2, 1, 4};
这个不会出现段错误,因为你合法地在堆栈中分配了一个4个整数的数组。
情景3
int *nums = {5, 2, 1, 4};
printf("%d\n", nums); // print 5
这个不会出现预期的段错误,因为你正在打印指针本身的值 - 而不是它的解除引用(这是无效的内存访问)。
<强>其他强>
每当你像这样对指针的值进行硬编码时,它几乎总是注定会发生段错误(因为确定哪个进程可以访问哪个内存位置是操作系统任务)。 / p>
int *nums = 5; // <-- segfault
因此,经验法则是始终初始化指向某个已分配变量地址的指针,例如:
int a;
int *nums = &a;
,或者
int a[] = {5, 2, 1, 4};
int *nums = a;
答案 2 :(得分:25)
int *nums = {5, 2, 1, 4};
是不正确的代码。有一个GCC扩展,它将此代码视为:
int *nums = (int *)5;
尝试形成一个指向内存地址5的指针。(这对我来说似乎不是一个有用的扩展,但我想开发人员基础想要它。)
要避免这种行为(或者至少得到警告),您可以在标准模式下进行编译,例如: -std=c11 -pedantic
。
有效代码的另一种形式是:
int *nums = (int[]){5, 2, 1, 4};
指向与nums
相同存储持续时间的可变文字。但是,int nums[]
版本通常更好,因为它使用更少的存储空间,您可以使用sizeof
来检测数组的长度。
答案 3 :(得分:12)
int *nums = {5, 2, 1, 4};
nums
是int
类型的指针。所以你应该指出一些有效的内存位置。 num[0]
您试图取消引用一些随机内存位置,从而导致分段错误。
是指针保持值为5,并且您试图取消引用它,这是系统上未定义的行为。 (看起来5
不是系统上的有效内存位置)
而
int nums[] = {1,2,3,4};
是一个有效的声明,您要说nums
是一个类型int
的数组,并根据初始化期间传递的元素数分配内存。
答案 4 :(得分:10)
指定{5, 2, 1, 4}
int *nums = {5, 2, 1, 4};
您将5分配给nums
(在从int到指向int的隐式类型转换之后)。取消它会在0x5
进行对内存位置的访问调用。您的程序可能无法访问。
尝试
printf("%p", (void *)nums);