为什么我在访问指针值时,在初始化指针时会收到警告:“(near initialization for 'ptr')”和运行时段错误?

3
以下是代码: 为什么我试图访问数组的第一个值时会出现分段错误? 这些警告都是什么意思?
#include<stdio.h>
int main(void)
{
    int *ptr = {1,2,3,4,5};//Is it not similar to char *ptr="Stackoverflow"?
    printf("%d\n",*ptr);// why Segmentation fault(core dumped) instead of 1
    return 0;
}

...
output:

warning: initialization makes pointer from integer without a cast [enabled by default] 
int *ptr = {1,2,3,4,5};
^

warning: (near initialization for ‘ptr’) [enabled by default]
warning: excess elements in scalar initializer [enabled by default]
warning: (near initialization for ‘ptr’) [enabled by default]
warning: excess elements in scalar initializer [enabled by default]
warning: (near initialization for ‘ptr’) [enabled by default]
warning: excess elements in scalar initializer [enabled by default]
warning: (near initialization for ‘ptr’) [enabled by default]
warning: excess elements in scalar initializer [enabled by default]
warning: (near initialization for ‘ptr’) [enabled by default]

1
int *ptr = {1,2,3,4,5}; 的意思是 int *ptr = (int *)1;,这没有任何意义。(这是GCC扩展,不符合标准C。)如果你想要一个数组,请使用 int ptr[] = {1,2,3,4,5}; - HolyBlackCat
可能是此链接的重复问题。 - Amber Beriwal
6个回答

5

//这不和char *ptr="Stackoverflow"很类似吗?

简而言之 不,它们不相同。


使用的初始化器为{1,2,3,4,5},这被称为花括号初始化器,旨在初始化元素的类型的值。它用于聚合或联合类型,正如在C11章节§6.7.9“初始化”中所述。

具有聚合或联合类型的对象的初始化器应为元素或命名成员的初始化器的花括号括起来的列表。

在这里,初始化器列表包含所有int值,并且您正在尝试通过它来初始化一个pointer,这是错误的。

此外,关于标量类型,引用C11第§6.2.5章:

算术类型和指针类型统称为标量类型。[...]

以及聚合类型

[...]数组和结构体类型统称为聚合类型。

这里存在许多问题,例如

  1. 您正在使用 int 值来初始化一个 int *
  2. 您最终会提供一个包含多个初始化元素的 花括号括起来的列表,用于一个 标量 对象。

因此,在您的代码中稍后,

 printf("%d\n",*ptr);

本质上是一种无效的内存访问,会引发未定义行为。段错误是其中的众多副作用之一。

谈到评论的重点,

char *ptr="Stackoverflow"?

对于char *ptr="Stackoverflow";,这里的"Stackoverflow"被称为字符串字面量,而ptr则被初始化为字符串字面量的基地址。


解决方案:
您需要拥有一个int数组,您可以使用花括号初始化器进行初始化。大致如下:
 int ptr[] = {1,2,3,4,5};

将是有效的。然后您可以像使用它一样。

 for(int i = 0; i < 5; i++)
    printf("%d\t", *(ptr+i));

我想补充一下,OP的代码实际上是用值“1”初始化了int *ptr,因为它是列表中的第一个。 - HolyBlackCat
1
@HolyBlackCat: 只有当忽略 (a) 使用 int1 初始化指针的约束违规以及 (b) 为不存在的对象提供初始化器时,这才是成立的。符合规范的编译器可以 (并且在我看来应该) 拒绝代码,这意味着没有任何东西被初始化。没有语言规则说如果有太多的初始值设定项,多余的部分将被忽略。 - Keith Thompson
@KeithThompson 是的,符合规范的编译器必须拒绝该代码,这也是带有-pedantic-errors选项的gcc的行为。但是如果没有此标志,它只会发出一些警告并将1放入指针中。(我应该提到我谈论的是没有标志的gcc行为。) - HolyBlackCat
@HolyBlackCat:不,符合标准的编译器只需要发出诊断即可。符合标准的编译器实际上需要无法编译的唯一事项是 #error 指令。 - Keith Thompson
@KeithThompson 哎呀,谢谢。我每天都会学到C++的新知识! - HolyBlackCat
@HolyBlackCat:我实际上是在参考C标准中的规则,但我认为C++也类似。 - Keith Thompson

3

你的原始代码是无效的。它至少包含两个约束违规:它为不存在的对象提供了初始化程序,并尝试将类型为int*的对象用1(类型为int)进行初始化。编译器可以(也应该)简单地拒绝它。gcc在仅警告错误后编译您的代码时过于宽容。生成的代码具有未定义的行为。

const char *cptr = "Hello";

上述内容是有效的。"Hello" 是一个数组类型的表达式(具体来说是char[6]类型)。在大多数情况下,包括这个例子,在这样的表达式中,它会被隐式转换为指向数组第0个元素的指针。请注意,我添加了const,这样编译器至少会警告我如果我试图修改cptr指向的数据。
int *iptr = { 1, 2, 3, 4, 5 }; // invalid

这是无效的。你可能希望它与 cptr 的处理方式类似。问题在于,{ 1, 2, 3, 4, 5 } 不是一个表达式;它仅在初始化程序中有效。它可以是数组对象的有效初始化程序,但由于不是表达式,因此不适用数组到指针的转换规则。

假设您的编译器支持 C99 或更高版本(特别是 复合字面量 特性),您可以编写:

int *iptr = (int[]){ 1, 2, 3, 4, 5 };

(这不是一个类型转换;语法类似,但{ ... }不是一个表达式。)

复合字面量是数组类型的表达式,具体来说是int[5],并应用了数组到指针的转换。

需要注意的是:字符串字面量创建了一个数组对象,并且该对象具有静态存储期,这意味着它存在于整个程序的执行过程中。如果复合字面量出现在任何函数之外,则创建具有静态存储期的对象;如果出现在函数内部,则创建具有自动存储期的对象,这意味着当你到达当前块的末尾时,它将停止存在。在这种情况下,它是在main函数内定义的,因此不太可能出现问题。但这是需要注意的。例如,以下代码是安全的:

const char *new_string(void) {
    const char *result = "hello";
    return result;
}

但这不是:
int *new_array(void) {
    int *result = (int[]){ 1, 2, 3, 4, 5 };
    return result; /* BAD! */
}

因为数组在离开函数时会停止存在。为了避免这种情况,您可以显式创建数组对象以使其静态化:
int *new_array(void) {
    static const int arr[] = { 1, 2, 3, 4, 5 };
    int *result = arr; /* or &arr[0] */
    return result;     /* or "return arr;" */
}

0

指针是一种标量数据类型,标准规定(C11-6.7.9):

标量的初始化器应为一个单个表达式,可选地用大括号括起来。

您无法使用括号括起来的初始化程序对具有多个表达式的标量数据类型进行初始化。

在某些情况下,

char *ptr="Stackoverflow";  

ptr 指向类型为 char 数组 的对象(请参阅标准 C11:§6.7.9/11)。它只是将 ptr 初始化为字符串字面值地址的开头。


0

尝试:

#include<stdio.h>
int main(void)
{
    int ptr[] = {1,2,3,4,5};//Is it not similar to char *ptr="Stackoverflow"?
    printf("%d\n",*ptr);// why Segmentation fault(core dumped) instead of 1
    return 0;
}

在原始代码中:

int *ptr = {1,2,3,4,5};

{1,2,3,4,5} 不能初始化整数数组。字符串初始化是一种特殊情况,不能应用于其他类型。

因此,这段代码将初始化一个指向内存地址0x00000001(初始块中的第一个地址)的整数指针。

该地址位于程序的范围之外,因此出现了分段错误。


是的。int 数组和 int 指针不是同一种类型。(而字面字符串是 char 指针类型。) - Jeff Y
@JeffY 你评论错了帖子。这个答案只是从原始问题中复制了 // 注释。 - dxiv
1
这个答案很好,但如果它指明了为什么这段代码是好的,原来的代码为什么不好,那将更好。 - anatolyg

0

0

您正在尝试将值存储在未初始化的指针中。在访问指针之前,必须将指针的值附加到内存位置。

只需在之前进行一些malloc

int $ptr;
ptr = malloc( 5* sizeof(int)); // since you have 5 values and then you can initialize your data
for (i=0;i<5;i++) {

(*ptr+i) = (i+1);

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接