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