如果 x 的类型是 char * 而不是 char[N],那么 printf("%s\n", x) 是如何工作的?

Posted

技术标签:

【中文标题】如果 x 的类型是 char * 而不是 char[N],那么 printf("%s\\n", x) 是如何工作的?【英文标题】:How does printf("%s\n", x) work, if x is of type char * instead of char[N]?如果 x 的类型是 char * 而不是 char[N],那么 printf("%s\n", x) 是如何工作的? 【发布时间】:2019-01-11 09:58:57 【问题描述】:

一些示例代码:

#include <stdio.h>

void func0(char *x)

  printf("func0, %s, %zu\n", x, sizeof(x));


//for comparison with func0
void func1(char **x)

  printf("func1, %s, %zu\n", *x, sizeof(*x));


int main()

  char x[10] = "hello";
  printf("%s, %zu, %zu, %zu\n", x, sizeof(x), sizeof(*x), sizeof("hello"));
  func0(x);
  func1(&x);
  return 0;

对于 func1()(用于与 func0 进行比较),存在“警告:不兼容的指针类型将 'char (*)[10]' 传递给 'char **' 类型的参数”和“分段错误”错误。

但是对于 func0(),'char [10]' 类型不是转换为 'char *' 吗?如果是这样,printf("%s") 怎么还能打印出“hello”?如func0()的输出所示:

hello, 10, 1, 6
func0, hello, 8

长度信息(即char[N]的N)是否传递给func0?

func0 中的 x 和 func1 中的 *x 有什么区别?它们不都是指向字符的地址吗?

非常感谢。


[编辑] 非常感谢您的详细回答!

让我总结一下 func1() 的警告和错误。

char x[10] = "hello";

&x 的类型是 char (*)[10],它是指向数组的指针,而不是指向指针的指针(例如 char **)。因此发出警告。

x 已经是地址(指向字符串),只能取实际内存空间的地址。 &x 不是 x 的地址。 **x in func1(): 试图从非法地址(本例中的“H”值?)读取,因此出现分段错误。


额外的问题是:

使用 &x 的用例是什么(例如 char (*) [N])?

我看到了 x 和 &x、char * / char[N] 与 char (*) [N] 之间的区别,但我无法确定必须使用 char(*) [N] 的情况。 x 和 &x 毕竟是同一个地址。

【问题讨论】:

C 中的 char* 字符串以零结尾。这意味着 printf 语句不知道字符串的长度,而只是打印每个字符,直到找到的第一个零字符。 @Adder,啊,有道理。那为什么func1中的printf会失败呢? char **x 是一个指向指针数组(实际上是你的字符串)的指针。使用它的值是&amp;(*x),它就是x -> printf("func1, %s, %lu\n", x, sizeof(x)); @Hearner:char **x 不是指向指针数组的指针。它是一个指向char 的指针。换句话说,它的类型不包括任何类型的数组。在指向的位置可能有一个指针数组,但这不是char **x 类型的一部分。 sizeof must be printed using %zu 【参考方案1】:

存在“警告:不兼容的指针类型将 char (*)[10] 传递给 char ** 类型的参数”和“分段错误”错误。

没错,&amp;x 表达式会生成一个指向十个字符数组的指针,因此会发出警告。如果你想将指针传递给指针,请创建一个指针,并获取它的指针:

char *px = x; // alternatively you could write &x[0]
func1(&px);

但是对于func0(),不是char [10]类型转换为char *吗?

没错,当你进行函数调用时,字符数组“衰减”为字符指针。

printf("%s") 怎么还能打印出"hello"

这是可能的,因为x 包含一个以空字符结尾的字符序列。所以printf在处理%s时不需要知道数组的大小:停在'\0'就足够了。

本质上,%s 不需要知道数组的大小,它需要知道字符串的长度,同样strlen“知道”它:

printf("func0, %s, %lu\n", x, strlen(x));
// Prints hello 5

我看到x (char / char[N]) 和&amp;x 之间的区别,但我不知道你必须使用char(*) [N] 的情况

这是一个小例子:

void print_many(char (*rows)[10], size_t count) 
    for (size_t i = 0 ; i != count ; i++) 
        printf("%zu: %s\n", i+1, rows[i]);
    

你这样称呼它:

char rows[10][] = "quick", "brown", "fox";
print_many(rows, 3);

Demo.

【讨论】:

是的! &x 不是 x 的地址!谢谢你的例子。字符 *px = x; px 是:-) 使用 &x 的用例是什么(其中 x 是 char[N] 类型)? @user7586189 当你确实需要一个指向数组的指针时,你会这样做——比如,迭代一个固定大小的数组数组。 但是 x(对于 char[N] 类型,没有 &)也能完成这项工作吗? @user7586189 char(*)[N] 是一个指向数组的指针,所以它不会衰减到另一种类型。只有数组衰减为指针。【参考方案2】:

长度信息(即char[N]的N)是否传递给func0?

没有。 func0 的参数是 char *。这是指向char 的指针,仅此而已。没有与之关联的长度。

但是对于func0(),'char [10]'类型不是转换成'char *'吗?

是的。当x(类型为char [10])用作函数参数时,它会自动转换为指向其第一个参数的指针。所以函数接收到一个指向char的指针。

如果是这样,printf("%s") 怎么还能打印出“hello”?

传递给printf 的指针必须指向以空字符为结尾的字符串的第一个字符。因此,它接收到的指针指向一个包含'h'char,然后是一个包含'e'char,然后是'l',然后是'l',然后是'o',然后是零。 printf 一个一个地打印字符,直到看到零。

func0中的x和func1中的*x有什么区别?

如果func1 被传递了一个指向char 的指针,那么使用*x 将一个指向char 的指针传递给printf 可能没问题。但是,您拨打func1 的电话是func1(&amp;x)。这不是将指针传递给指向char 的指针。因为x 是一个由10 个char 组成的数组,所以&amp;x 是一个指向一个由10 个char 组成的数组的指针。它不是指向指针的指针。

数组不是指针。尽管数组通常会自动转换为指针,但当数组是一元 &amp; 的操作数时,不会发生这种自动转换。 (当数组是sizeof_Alignof 的操作数,或者当数组是用于初始化数组的字符串字面量时,也不会发生这种情况。)

要从数组x 中生成指向char 的指针,您必须将指针的地址指向其第一个字符。在简单的表达式中无法做到这一点,因为x 是数组,而不是指针。您可以使用char *y = x; 创建指向其第一个字符的指针,然后您可以将&amp;y 传递给func1

注意

不要使用%lu 打印来自sizeof 的尺寸。使用%zuz 修饰符专门用于尺寸类型size_t

【讨论】:

是的,应该使用 "%zu":-) 使用 &x 的用例是什么(其中 x 是 char[N] 类型)?【参考方案3】:

对于标题中的问题,print("%s\n", x) 不关心x 是指针还是数组,只要它包含以零结尾的字符串即可。

接下来,只有数组的最高维度可以透明地强制转换为指针,这就是警告的内容。

最后,&amp;x 指向数组的零元素,因此当您在func1 中取消引用它时,数组的前几个元素(甚至可能是其中的八个,所以最右边的两个甚至没有初始化)被重新解释作为指向 char 的指针,几乎可以肯定地指向无处。段错误就是这样产生的。

【讨论】:

以上是关于如果 x 的类型是 char * 而不是 char[N],那么 printf("%s\n", x) 是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章

可以/为啥在返回类型中使用 char * 而不是 const char * 会导致崩溃?

为啥我应该使用 char 而不是 varchar? [复制]

怎么把int转换为char类型

如何将unsigned char 转化为int

C++ 输出char问题

有没有更好的方法来声明一个 CStringT<> 的 char 类型的适当实例而不是