"int*nums = {5,2,1,4}"会导致分段错误

use*_*784 81 c arrays pointers

int *nums = {5, 2, 1, 4};
printf("%d\n", nums[0]);
Run Code Online (Sandbox Code Playgroud)

导致段错误,而

int nums[] = {5, 2, 1, 4};
printf("%d\n", nums[0]);
Run Code Online (Sandbox Code Playgroud)

没有.现在:

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);
Run Code Online (Sandbox Code Playgroud)

打印5.

基于此,我猜想数组初始化符号{}会盲目地将这些数据加载到左边的任何变量中.当它是int []时,数组将根据需要填充.当它是int*时,指针被填满5,并且存储指针之后的存储器位置被填充为2,1和4.因此nums [0]尝试deref 5,导致段错误.

如果我错了,请纠正我.如果我是正确的,请详细说明,因为我不明白为什么数组初始化程序按照它们的方式工作.

Lun*_*din 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};
Run Code Online (Sandbox Code Playgroud)

或者,如果要创建指针数组:

int* ptr[] = { /* whatever makes sense here */ };
Run Code Online (Sandbox Code Playgroud)

编辑

经过一些研究,我可以说"多余元素初始化列表"确实无效C - 它是GCC扩展.

标准6.7.9初始化说(强调我的):

2 初始化程序不应尝试为未初始化的实体中包含的对象提供值.

/ - /

11 标量的初始值设定项应为单个表达式,可选择用大括号括起来.对象的初始值是表达式的初始值(转换后); 与简单赋值相同的类型约束和转换适用,将标量的类型作为其声明类型的非限定版本.

"标量类型"是一个标准术语,指的是不属于数组,结构或联合类型的单个变量(称为"聚合类型").

因此,在简单的英语中,标准说:"当你初始化变量时,可以随意在初始化表达式周围抛出一些额外的括号,只因为你可以."

  • 使用`{}`中包含的单个值初始化标量对象的能力并不"愚蠢".相反,它促进了C语言中最重要和最方便的习惯之一 - "{0}"作为通用零初始化器.C中的所有内容都可以通过`= {0}`进行零初始化.这对于编写与类型无关的代码非常重要. (11认同)
  • @Lundin:是的,有.什么机制初始化对象的哪个部分完全无关紧要.是否具体地允许"{}"标量的初始化也是完全无关紧要的.唯一重要的是`= {0}`初始化器保证*零初始化整个对象*,这正是使它成为C语言的经典和最优雅的习语之一. (4认同)
  • @AnT没有"通用零初始化器"这样的东西.在聚合的情况下,"{0}"仅意味着将第一个对象初始化为零并初始化其余对象,就像它们具有静态存储持续时间一样.我会说这是巧合,而不是一些"通用初始化器"的故意语言设计,因为`{1}`不会将所有对象初始化为1. (3认同)
  • @Lundin C11 6.5.16.1/1涵盖`p = 5;`(没有列出的情况用于将整数赋给指针); 6.7.9/11表示赋值约束也用于初始化. (3认同)
  • @Lundin:我还完全不清楚你对"{1}"的评论与这个话题有什么关系.没有人声称`{0}`将'0`解释为聚合的每个成员的多初始化器. (2认同)

art*_*rtm 28

情景1

int *nums = {5, 2, 1, 4};    // <-- assign multiple values to a pointer variable
printf("%d\n", nums[0]);    // segfault
Run Code Online (Sandbox Code Playgroud)

为什么这是一个段错误?

你声明nums为int的指针 - 它nums应该保存内存中一个整数的地址.

然后,您尝试初始化nums多个值的数组.因此,在不深入细节的情况下,这在概念上不正确的 - 将多个值分配给应该包含一个值的变量是没有意义的.在这方面,如果你这样做,你会看到完全相同的效果:

int nums = {5, 2, 1, 4};    // <-- assign multiple values to an int variable
printf("%d\n", nums);    // also print 5
Run Code Online (Sandbox Code Playgroud)

在任何一种情况下(为指针或int变量分配多个值),接下来会发生变量将获得第一个值5,而忽略其余值.此代码符合但您会收到不应该在赋值中的每个附加值的警告:

warning: excess elements in scalar initializer.

对于为指针变量分配多个值的情况,程序在您访问时会发生段错误nums[0],这意味着您将从字面上引用存储在地址5中的任何内容.nums在这种情况下,您没有为指针分配任何有效内存.

值得注意的是,对于为int变量分配多个值的情况没有段错误(这里没有解除引用任何无效指针).


情景2

int nums[] = {5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)

这个不会出现段错误,因为您在合法分配堆栈中包含4个整数的数组.


情景3

int *nums = {5, 2, 1, 4};
printf("%d\n", nums);   // print 5
Run Code Online (Sandbox Code Playgroud)

这个不会像预期的那样发生段错误,因为你正在打印指针本身的值 - 而不是它解除引用的内容(这是无效的内存访问).


其他

每当你像这样的指针的值进行硬编码时,它几乎总是注定会发生段错误(因为确定哪个进程可以访问哪个内存位置是操作系统任务).

int *nums = 5;    // <-- segfault
Run Code Online (Sandbox Code Playgroud)

因此,经验法则是始终初始化指向某个已分配变量地址的指针,例如:

int a;
int *nums = &a;
Run Code Online (Sandbox Code Playgroud)

要么,

int a[] = {5, 2, 1, 4};
int *nums = a; 
Run Code Online (Sandbox Code Playgroud)

  • "这是有效的" - 忽略多余的初始化程序是GCC扩展; 在标准C中是不允许的 (3认同)
  • +1这是一个很好的建议,但鉴于许多平台上的魔术地址,"从不"真的太强大了.(对于那些固定地址使用常量表并不指向现存的变量,因此违反了规定的规则.)驱动程序开发等低级别的东西经常处理这种事情. (2认同)

M.M*_*M.M 25

int *nums = {5, 2, 1, 4};是不正确的代码.有一个GCC扩展,它将此代码视为:

int *nums = (int *)5;
Run Code Online (Sandbox Code Playgroud)

试图形成一个指向内存地址5的指针.(这对我来说似乎不是一个有用的扩展,但我想开发人员基地想要它).

为了避免这种行为(或者至少得到警告),你可以在标准模式下编译,例如-std=c11 -pedantic.

另一种有效代码形式是:

int *nums = (int[]){5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)

它指向相同存储持续时间的可变文字nums.但是,int nums[]版本通常更好,因为它使用更少的存储空间,您可以使用它sizeof来检测数组的长度.


Gop*_*opi 12

int *nums = {5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)

nums是一个类型的指针int.所以你应该指出一些有效的内存位置.num[0]您试图取消引用一些随机内存位置,从而导致分段错误.

是的,指针保持值为5,您试图取消引用它,这是系统上未定义的行为.(看起来5不是您系统上的有效内存位置)

int nums[] = {1,2,3,4};
Run Code Online (Sandbox Code Playgroud)

是一个有效的声明,你说的nums是一个类型的数组,int并根据初始化期间传递的元素数分配内存.


Fah*_*qui 10

通过分配 {5, 2, 1, 4}

int *nums = {5, 2, 1, 4};
Run Code Online (Sandbox Code Playgroud)

你正在分配5 nums(在从int到指向int的隐式类型转换之后).取消它会对内存位置进行访问调用0x5.您的程序可能无法访问.

尝试

printf("%p", (void *)nums);
Run Code Online (Sandbox Code Playgroud)