c - 为什么解引用指向字符串(char数组)的指针会返回整个字符串而不是第一个字符?

标签 c arrays string pointers pointer-arithmetic

由于指向数组的指针指向数组的第一个元素(具有相同的地址),我不明白为什么会发生这种情况:

#include <stdio.h>

int main(void) {    
    char (*t)[] = {"test text"};
    printf("%s\n", *t + 1); // prints "est text"
}

另外,为什么下面的代码会打印2呢?
#include <stdio.h>

int main(void) {    
    char (*t)[] = {1, 2, 3, 4, 5};
    printf("%d\n", *t + 1); // prints "2"
}

最佳答案

在写这个答案的时候,所有其他的答案都是错误的。此外,你的问题闻起来像是一个an XY problem的问题,因为你尝试的构造很可能不是你想要的。你真正想做的只是:

char *t = "test text";
printf("%s\n", t);  // prints "test text"


printf("%c\n", t[1]); // prints "e", the 2nd character in the string.

但既然你想知道为什么会发生这些事,而且所有其他的解释都是错误的,那么下面是:
声明将t声明为指向char数组的指针:
cdecl> explain char (*t)[];
declare t as pointer to array of char

不是其他人建议的指针数组。此外,*t的类型是不完整的,因此您无法获取其大小:
sizeof *t;

会导致
error: invalid application of ‘sizeof’ to incomplete type ‘char[]’
     sizeof *t;

在编译时。
现在,当您尝试用
 char (*t)[] = {"test text"};

它会发出警告,因为当"test text"是一个(常量)char数组时,它会衰减为指向char的指针。另外,这里的大括号也没用,上面的节选等于写:
char (*t)[] = "test text";

不像
int a = 42;


int a = {42};

是同义词。我是C。
要获取指向数组的指针,必须在数组上使用“address of”运算符(字符串文本!),以避免它衰减为指针:
char (*t)[] = &"test text";

现在t被正确初始化为指向char的(不可变)数组的指针。然而,在您的例子中,使用指向不正确类型的指针并不重要,因为尽管这两个指针的类型不兼容,但它们指向相同的地址-仅,一个指向char数组,另一个指向char数组中的第一个字符;因此观察到的行为是相同的。
当您取消引用指向-t数组的指针char时,您将获得-char数组的定位值(左值)。char数组的左值在正常情况下会衰减为指向第一个元素的指针(通常如此),因此*t + 1现在将指向该数组中的第二个字符;然后printf使用该值将从该指针开始打印以0结尾的字符串的内容。
%s的行为在C11(n1570)中规定为
[抄送]
如果不存在长度修饰符,则参数应是指向
字符类型数组的元素。数组中的字符是
写入(但不包括)终止空字符。[…]如果
精度未指定或大于数组大小,数组应
包含空字符。[...]
(强调我的。)
至于第二次初始化:
char (*t2)[] = {1, 2, 3, 4, 5};

如果使用最新版本的GCC编译它,默认情况下会收到很多警告,首先:
test.c:10:19: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
   char (*t2)[] = {1, 2, 3, 4, 5};
                   ^

因此%s将从l转换为指向-1数组的指针,而无需任何转换。
然后,在剩余的值中,编译器将抱怨:
y.c:10:19: note: (near initialization for ‘t2’)
y.c:10:21: warning: excess elements in scalar initializer
   char (*t2)[] = {1, 2, 3, 4, 5};
                      ^

也就是说,在你的例子中,2,3,4和5被默默地忽略了。
因此,指针的值现在是1,例如在x86平面内存模型上,它将指向内存位置1(尽管这是自然定义的实现):
printf("%p\n", (void*)t2);

打印(双实现定义)
0x1

当您取消引用这个值(它是指向char数组的指针)时,您将得到从内存地址1开始的char数组的左值。当您添加1时,这个char左值数组将衰减为指向char的指针,结果您将得到int这是指向-char的指针,其值为((char*)1) + 1。可根据GCC(5.4.0)默认生成的警告验证该值的类型:
y.c:5:10: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘char *’ [-Wformat=]
   printf("%d\n",*t2+1); //prints "2"
          ^

参数的类型是char
现在,将2作为参数传递给char *,使用(char*)2进行转换,这需要printf。这具有未定义的行为;在您的情况下,%d的字节模式被充分混淆地解释为int并因此被打印。
现在人们意识到打印的值与初始值设定项中的(char*)2无关:
#include <stdio.h>

int main(void) {
    char (*t2)[] = {1, 42};
    printf("%d\n", *t2 + 1);
}

仍将打印2,而不是2。量化宽松政策。
或者,对于这两种初始化,您可以使用C99复合文本来初始化:
// Warning: this code is super *evil*
char (*t)[] = &(char []) { "test text" };
char (*t2)[] = &(char []) { 1, 2, 3, 4, 5 };

虽然这可能比你想要的更少,而且最终的代码在C89或C++编译器中没有任何编译的机会。

关于c - 为什么解引用指向字符串(char数组)的指针会返回整个字符串而不是第一个字符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39155012/

相关文章:

c - 安西 C : Attempting to count total chars in a file

c - C99 中的哪一节说数组在 C 中没有复制地传递给函数?

java - 大阵列比较

c - 矩阵中总和最大的行

c++ - 成员函数erase()不在循环中工作

C while循环仅在语句为真时运行?

c - 关于 Visual Studio 项目文件的问题

javascript - Chai 期望列表包含字符串类型的项目

r - 从具有 H、M、S - R 编程的字符串中分割时间值

python - 连接正则表达式从子进程 STDERR 检索到的字符串会导致困惑