问题描述
我有一个 char pointer
带有像这样的字符串文字 char *d="abc";
并且我正在像这样递增它
*(d+1)
如果我执行 b
,我会得到类似 printf("%c",*(d+1))
的结果
但是当我有这些行
char *c={'a','b','c'}
printf("%c\n",*(c+1)); /// CAUSES SegFault
上面的行抛出异常。当我尝试使用 gdb 进行回溯并打印 *(c+1) 时,它说 $1 = 0x61 <error: Cannot access memory at address 0x61>
所以我的问题是为什么这与我将字符串文字分配给 char pointer
当我将 int
数组分配给 int pointer
并以这种方式递增时会发生同样的情况
解决方法
非常感谢@nielsen 指出这一点,在他们发表评论后一切都变得清晰了。
首先,让我们尝试一个不会出现段错误的类似程序:
#include <stdio.h>
int main()
{
char *a = {'a','b','c'};
printf("%p\n",(void *) a);
}
对我来说,这输出:0x61
。这应该会敲响警钟,它与 GDB 提供的地址相同。
然而,更重要的是我收到的警告:
main.c:5:16: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
char *a = {'a','c'};
^~~
main.c:5:16: note: (near initialization for ‘a’)
main.c:5:21: warning: excess elements in scalar initializer
char *a = {'a','c'};
main.c:5:16: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
char *a = {'a','c'};
main.c:5:21: warning: excess elements in scalar initializer
char *a = {'a','c'};
^~~
main.c:5:21: note: (near initialization for ‘a’)
main.c:5:26: warning: excess elements in scalar initializer
char *a = {'a','c'};
^~~
main.c:5:26: note: (near initialization for ‘a’)
initialization makes pointer from integer without a cast [-Wint-conversion]
已经在评论中指出了。然而,随着另一个警告,这变得很清楚:
main.c:5:21: warning: excess elements in scalar initializer
char *a = {'a','c'};
基本上,这并不像您认为的那样。在所有。 {}
是一个“标量”初始值设定项。来自 https://en.cppreference.com/w/c/language/type 的摘录:
标量类型:算术类型和指针类型
指针恰好是标量类型,因为它只能保存 1 个值,也就是地址。因此编译器将只使用 'a'
来初始化 c
,因为 c
只能保存 1 个值,并忽略其他所有内容(再次,标量)。十六进制 'a'
的 ASCII 值是多少? 61,与 GDB 指出的地址完全相同。希望您了解现在的情况:
-
当编译器看到
char *c = {'a','c'};
时,它会将聚合初始值设定项视为标量初始值设定项,因为c
是标量变量,因此只取'a'
并告诉您2 个额外字符。 -
'a'
是一个int
文字,被隐式转换为char *
并成为一个地址。编译器也会就此向您发出警告。 -
您尝试打印
*(c + 1)
,但由于这是一个无效地址/您不能触摸该地址,因此发生了段错误。
我认为您真正想做的是将 c
视为一个数组。为此,您可以将 c
的类型更改为数组:
char c[] = {'a','c'};
或者将 c
保留为 char *
并使用复合文字:
char *c = (char []) {'a','c'};
然而,char *c = {'a','c'};
不是有效的 C,因为大括号括起来的标量初始值设定项只允许保存 1 个表达式。弗拉德的回答给出了证明这一点的标准的具体引述。使用 -pedantic-errors
编译此代码会将此处提到的所有警告替换为错误。
这个你忘记加分号的声明
char *c={'a','c'};
^^^^
不是 C 中的有效构造。您不能使用包含多个初始化程序的花括号列表初始化标量对象。
来自 C 标准(6.7.9 初始化)
11 标量的初始值设定项应为单个表达式, 可选用大括号括起来。 对象的初始值是 表达式的(转换后);同类型约束 和适用于简单赋值的转换,采用 标量是其声明类型的非限定版本。
因此编译器会发出错误消息,实际上没有什么可讨论的,因为您的程序将无法成功编译。
你可以写例如
char *c = { ( 'a','c' ) };
在这种情况下,带有逗号运算符的表达式将用作初始化表达式。这个初始化等价于
char *c = { 'c'};
因此指针c
由字符'c'
的内码初始化。例如,如果使用了 ASCII 表,那么上面的初始化等价于
char *c = 99;
编译器应该再次发出一条消息,表明您正在尝试用整数初始化指针。
由于用作地址的值 99
未指向程序中的有效对象,因此此语句
printf("%c\n",*(c+1));
调用未定义的行为。
或者您可以使用例如复合文字来初始化指针 c
,如下面的演示程序所示
#include <stdio.h>
int main(void)
{
char *c = ( char [] ){ 'a','c' };
printf( "%c\n",*(c+1) );
return 0;
}
在这种情况下,您将获得预期的结果。与指向字符串字面量的指针的唯一区别是,在程序中,指针 c
不指向字符串。但是你可以像
char *c = ( char [] ){ 'a','c','\0' };
它会指向一个字符串。