【问题标题】:"int *nums = {5, 2, 1, 4}" causes a segmentation fault"int *nums = {5, 2, 1, 4}" 导致分段错误
【发布时间】:2016-05-17 23:00:42
【问题描述】:
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,导致segfault。

如果我错了,请纠正我。如果我是正确的,请详细说明,因为我不明白为什么阵列initializers按照他们的方式工作。

【问题讨论】:

  • 与所有启用的所有警告编译,您的编译器应告诉您发生什么。 span>
  • @GSerg 这绝不是重复。这个问题中没有数组指针。尽管该帖子中的某些答案与此处的答案相似。
  • @Lundin 我有 30% 的把握,所以我没有投票关闭,只发布了链接。
  • 养成使用 -pedantic-errors 标志运行 GCC 的习惯并观察诊断。 int *nums = {5, 2, 1, 4}; 是无效的 C。

标签: c arrays pointers


【解决方案1】:

C 中有一条(愚蠢的)规则说任何普通变量都可以用大括号括起来的初始化列表初始化,就像它是一个数组一样。

比如你可以写int x = {0};,完全等价于int x = 0;

因此,当您编写int *nums = {5, 2, 1, 4}; 时,您实际上是在为单个指针变量提供一个初始化列表。然而,它只是一个单一的变量,所以它只会被分配第一个值 5,列表的其余部分被忽略(实际上我不认为具有过多初始化程序的代码甚至应该使用严格的编译器编译) - 它不会完全写入内存。该代码相当于int *nums = 5;。这意味着,nums应该指向地址5

此时您应该已经收到两个编译器警告/错误:

  • 将整数分配给指针而不进行强制转换。
  • 初始化列表中的元素过多。

然后代码当然会崩溃和烧毁,因为5 很可能不是一个有效地址,您可以使用nums[0] 取消引用。

作为旁注,您应该使用 %p 说明符指定 printf 指针地址,否则您将调用未定义的行为。


我不太确定你想在这里做什么,但如果你想设置一个指向数组的指针,你应该这样做:

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 标量的初始值设定项应为单个表达式, 可选地用大括号括起来。 对象的初始值是 表达式的(转换后);相同的类型约束和 与简单赋值一样的转换适用,采用 标量是其声明类型的非限定版本。

“标量类型”是一个标准术语,指的是非数组、结构或联合类型的单个变量(称为“聚合类型”)。

所以用简单的英语标准说:“当你初始化一个变量时,随意在初始化表达式周围加上一些额外的大括号,因为你可以。”

【讨论】:

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

场景 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] 时,程序会出现段错误,这意味着您正在按照字面意思推迟存储在 address 5 中的任何内容。在这种情况下,您没有为指针 nums 分配任何有效内存。

值得注意的是,在为 int 变量分配多个值的情况下没有段错误(您在这里没有取消引用任何无效指针)。


场景 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

所以经验法则是总是初始化一个指向某个已分配变量地址的指针,例如:

int a;
int *nums = &a;

或者,

int a[] = {5, 2, 1, 4};
int *nums = a; 

【讨论】:

  • +1 这是个好建议,但考虑到许多平台的魔法地址,“从不”真的太强了。 (对那些固定地址使用常量表并不指向现存变量,因此违反了您所说的规则。)像驱动程序开发这样的低级东西经常处理这种事情。
  • "这是有效的" - 忽略多余的初始化器是 GCC 扩展;在标准 C 中是不允许的
  • @TheNate - 是的,你是对的。我根据您的评论进行了编辑 - 谢谢。
  • @M.M - 感谢您指出这一点。我编辑删除了它。
【解决方案3】:

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 来检测阵列的长度。

【讨论】:

  • 复合字面量形式的数组是否保证至少与nums一样长?
  • @supercat 是的,如果 nums 是自动的,它是自动的,如果 nums 是静态的,它是静态的
  • @MM: 即使nums 是在函数中声明的静态变量,这是否适用,或者编译器是否有权将数组的生命周期限制为封闭块的生命周期,即使它被分配给一个静态变量?
  • @supercat 是(第一位)。第二个选项意味着第二次调用函数时的 UB(因为静态变量仅在第一次调用时初始化)
【解决方案4】:
int *nums = {5, 2, 1, 4};

numsint 类型的指针。所以你应该把这一点指向一些有效的内存位置。 num[0] 您正试图取消引用一些随机内存位置,从而导致分段错误。

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

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

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

【讨论】:

  • "是的,指针保持值 5,您正试图取消引用它,这是未定义的行为。"一点也不,这是非常好的和明确定义的行为。但是在 OP 使用的系统上,它不是有效的内存地址,因此会崩溃。
  • @Lundin 同意。但我认为 OP 从来不知道 5 是一个有效的内存位置,所以我谈到了这些。希望编辑有所帮助
  • 应该是这样的? int *nums = (int[]){5, 2, 1, 4};
【解决方案5】:

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

int *nums = {5, 2, 1, 4};

您将 5 分配给 nums(在从 int 隐式类型转换为指向 int 的指针之后)。取消引用它会访问0x5 的内存位置。这可能不允许您的程序访问。

试试

printf("%p", (void *)nums);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-30
    • 1970-01-01
    • 2011-11-06
    • 2020-12-31
    相关资源
    最近更新 更多