“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是一个inttypes的指针。 所以你应该把这点指向一些有效的内存位置。 num[0]你试图解引用一些随机的内存位置,因此分段错误。

是的,指针的值是5,而你试图取消它在系统上的未定义行为。 (看起来像5不是你的系统上有效的内存位置)

 int nums[] = {1,2,3,4}; 

是一个有效的声明,你说nums是一个inttypes的数组,而内存是根据初始化过程中传递的元素数量来分配的。

通过分配{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);