为什么取消引用char**值(指针到指针到字符)不同于取消引用char*[](指针到字符数组)?

kg7wmglp  于 2023-01-29  发布在  其他
关注(0)|答案(2)|浏览(132)

如果char* 数组至少定义了一个值,则第一个值可以通过“arr[0]”索引,并使用格式说明符“%s”成功打印。但是,如果定义了char**,则在尝试使用printf或类似函数(使用字符串格式说明符)打印第一个元素时,将出现segfault。
下面是演示其工作原理的代码块:

char* s = "test";
char** sa = { s };
char* arr[] = { s };

// Prints pointers of each variable
puts("\nTest 1:");
puts("----------------------------------------");
printf("s:\t%p\n", s);
printf("sa:\t%p\n\n", sa);

// Dereferences pointers, prints as pointers (even though *s is a char)
puts("Test 2:");
puts("----------------------------------------");
printf("s:\t%p\n", *s);
printf("sa:\t%p\n\n", *sa);

// Prints strings of each variable
puts("Test 3:");
puts("----------------------------------------");
printf("s:\t%s\n", s);
printf("sa:\t%s\n\n", sa);

// Prints first element of array (works unlike *(char**), which I would think are the same)
puts("Test 4:");
puts("----------------------------------------");
printf("arr:\t%s\n", arr[0]);
printf("arr:\t%s\n\n", *arr);

// Prints strings of dereferenced pointers (segfault for sa)
puts("Test 5:");
puts("----------------------------------------");
printf("s:\t%s\n", *s);
printf("sa:\t%s\n\n", *sa);

以下是潜在输出:

Test 1:
----------------------------------------
s:      0x10013bf2c
sa:     0x10013bf2c

Test 2:
----------------------------------------
s:      0x74
sa:     0x65540a0074736574

Test 3:
----------------------------------------
s:      test
sa:     test

Test 4:
----------------------------------------
arr:    test
arr:    test

Test 5:
----------------------------------------
zsh: segmentation fault  ./test2

我知道解引用变量 s 返回第一个字符的ASCII值,在本例中是“t”。但是,我不明白为什么变量 sa 不解引用数组中的第一个字符串?解引用双精度指针不应该返回指向 s 的指针吗?
我尝试过从指针 sa 返回字符串,但它只返回segfaults,这意味着Test 2下的 sa 指针没有意义,也与绑定到 s 的字符串没有关联。

j8yoct9x

j8yoct9x1#

对于初学者来说,您没有数组。在此声明中

char** sa = { s };

你声明了一个char **类型的指针,它由char *类型的变量s的值初始化。

char* s = "test";
char** sa = { s };

编译器应该发出一条消息,说明这两种指针类型之间没有隐式转换。
这两个输出之间的差值

printf("s:\t%p\n", *s);
printf("sa:\t%p\n\n", *sa);

在第一种情况下,表达式*s的类型char被提升为类型int,也就是说,只有字符串字面量的第一个字节被读取为值。
在第二种情况下,表达式*sa被认为是指针类型char *的表达式,因此函数读取表达式所占用的内存,作为存储大小为48字节的指针的内存,这取决于所使用的系统,而没有第一种情况下的任何提升。
至于你对我的回答的评论
此外,当试图使用格式说明符“%s”将sa的char* 版本(当sa被解除引用时)打印为字符串时,会发生segfault。
当指针sa被解引用时,它的值是由存储在字符串常量中的字符组成的值,并且当使用转换说明符%s时,该值被解释为指针。
查看问题中显示的代码的输出

sa:     0x65540a0074736574

输出值74736574的这一部分表示以相反顺序"tset"写入的字“test”。
你可以写信代替

char* s = "test";
char** sa = { &s };

然后

printf( "%s\n", *sa );

在这种情况下,指针sa将包含声明的指针s的地址,表达式*sa产生存储在指针s中的值,该值是字符串文字量"test"的第一个字符的地址。

mklgxw1f

mklgxw1f2#

问题是char **char *类型不兼容--正如您可以清楚地看到的那样,为了检索原始的char对象,需要应用更多的间接寻址。
在C中,你可以使用初始化语法({})来赋值给非数组类型,所以char** sa = { s };等价于=的简单赋值。
然后你通过执行sa来访问s指针,然后sa*sa间接定向,这里我们有潜在的UB,因为*sa产生了一个对象,不能保证它有有效的位置。

  • 当printf试图访问上述位置时,UB实际上发生 *

这是因为(例如在32位架构中)*sa阅读s字符串的4个字节,并将它们解释为地址(取决于字节序)。
两者的区别:

char** sa = { s };
char* arr[] = { s };

在第一种情况下{}是多余的,并不意味着什么,而在第二种情况下,它们指的是我们正在初始化数组的嵌套元素(即char *s,也是char *)。这将创建(对于第二种情况)一个具有单个元素的数组。
数组和指针是有区别的。

相关问题