如果我在此示例中有一个指向变量'bs'的指针:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char **bs = {"this", "is", "a", "test"};
puts(bs);
return 0;
}
puts语句在屏幕上显示“this”。如何让它显示'是'等等。我虽然可以做类似的事情:
puts(bs+1);
在指针变量中添加数字实际上做了什么?它是指向下一个地址,还是将指针的类型考虑在内并使用它指向的变量的大小来确定下一个变量在内存中的起始位置?
答案 0 :(得分:3)
puts语句在屏幕上显示“this”。
这只是(坏)好运,如果编译器根本不拒绝编译它,代码会调用未定义的行为。
clang(默认情况下)为代码生成的警告是
$ clang badpoint.c
badpoint.c:6:18: warning: incompatible pointer types initializing 'char **' with an
expression of type 'char [5]' [-Wincompatible-pointer-types]
char **bs = {"this", "is", "a", "test"};
^~~~~~
badpoint.c:6:26: warning: excess elements in scalar initializer
char **bs = {"this", "is", "a", "test"};
^~~~
badpoint.c:8:10: warning: incompatible pointer types passing 'char **' to parameter of
type 'const char *'dereference with * [-Wincompatible-pointer-types]
puts(bs);
^~
*
/usr/include/stdio.h:688:32: note: passing argument to parameter '__s' here
extern int puts (__const char *__s);
^
3 warnings generated.
gcc为每个多余的初始化程序生成一个警告,而clang只为三个中的第一个生成一个,否则gcc(也是默认情况下)的警告信息量不大,但是相同。
那是怎么回事?
您正在尝试初始化标量(char**
),并提供{"this", "is", "a", "test"}
,包含四个初始值设定项的括号内的初始化列表秒。每6.7.9(11)
标量的初始值设定项应为单个表达式,可选择用大括号括起来。对象的初始值是表达式的初始值(转换后);与简单赋值相同的类型约束和转换适用,将标量的类型作为其声明类型的非限定版本。
违反“必须”要求,因此会调用未定义的行为。
如果编译器没有终止那里的翻译,它可能只是忽略了多余的初始值设定项 - clang和gcc都这样做 - 并将声明视为
char **bs = "this";
导致从不兼容类型初始化警告,因为它尝试使用char**
初始化char[5]
,但是根据6.5.16.1,初始化程序的类型必须与{{1}兼容}}
然而,如果编译器成功翻译了程序,则很可能导致char**
包含字符串文字中第一个bs
的地址(数组char
被转换为指向赋值运算符右侧第一个元素的指针。
然后是电话
"this"
将错误类型(puts(bs)
)的指针传递给char**
,这是一个约束违规,需要来自编译器的诊断消息(并使程序无效)。
但指针恰好包含正确的地址,未定义的行为表现为程序打印puts
。
我如何才能让它显示'是'等等
通过更正程序。因此,字符串"this"
,"is"
和"a"
甚至不会出现在由gcc或clang生成的目标文件中。
纠正它的一种方法是将声明改为
"test"
这样char *bs[] = {"this", "is", "a", "test"};
是一个四个bs
的数组,指向四个字符串的(各自的第一个元素)。
然后您必须将通话调整为char*
,解除引用或订阅puts
,
bs
打印puts(bs[0]);
;
"this"
在不同的行上打印所有四个字符串。