6

为什么用 %s 打印空字符 ('\0', 0) 实际上会打印 "(null)" 字符串?

像这样的代码:

char null_byte = '\0';
printf("null_byte: %s\n", null_byte);

...印刷:

null_byte: (null)

...它甚至在 Valgrind 下运行没有错误,我得到的只是编译器警告warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat](注意:我在 32 位 Ubuntu 上使用 gcc 4.6.3)

4

6 回答 6

10

这是未定义的行为,但它发生在您的实现中:

  • 您传递的int0 值%s作为空指针读取
  • %sby的处理printf具有特殊情况代码来识别空指针和 print (null)

这些都不是标准所要求的。需要 [*] 的部分是charvarargs 中使用的 a 作为int.

[*] 好吧,鉴于在您的实现中,所有的值char都可以表示为int. 如果你在一些有趣的实现中char是无符号的并且宽度与 相同int,它将被传递为unsigned int。我认为有趣的实现将符合标准。

于 2012-12-03T10:32:50.807 回答
4

好吧,对于初学者来说,你做错了。'\0'是一个字符,应该用%cand not打印%s。我不知道这是否是出于实验目的而故意的。

的实际二进制值\0是 0。您试图将值 0 转换为char *指针,这将导致无效引用和崩溃。您的编译器通过对值进行特殊处理来防止这种情况发生%s

Valgrind 不会捕获它,因为它运行在生成的二进制文件上,而不是源代码上(你需要一个静态分析器来代替)。由于编译器已经将该调用转换为安全的“空指针”文本,因此 valgrind 不会发现任何问题。

于 2012-12-03T10:34:11.837 回答
1

null_byte 包含 0。当您在 printf 中使用 %s 时,您正在尝试打印一个字符串,该字符串是一个 char 的地址(一个 char *)。您在代码中所做的是将地址 0 (NULL) 作为字符串的地址传递,这就是输出为空的原因。编译器警告您将错误的类型传递给 %s 修饰符。试试 printf("null_byte: %s\n", &null_byte);

于 2012-12-03T10:36:29.567 回答
0

您的printf语句正在尝试打印一个字符串,因此将值解释null_byechar *值为 null 的 a。注意警告。要么这样做

printf("null_byte: %s\n", &null_byte);

或这个

printf("null_byte: %c\n", null_byte);
于 2012-12-03T10:33:31.140 回答
0

因为printf是可变参数,所以通常的参数提升被执行,null_byte所以它被提升(强制转换)到int, value 0

printf然后读取一个char *指针,并将0int 解释为空指针。您的 C 标准库具有将空字符串打印为(null).

于 2012-12-03T10:34:42.383 回答
0

在 XV6 中为其他答案添加一个实现示例,这是 Unix v6 的教育重新实现,如果您将零值传递给%s,它会打印(null)

void printf(int fd, const char *fmt, ...) {
    uint *ap = (uint*)(void*)&fmt + 1;
    ...
    if(state == '%') {
        ...
        if(c == 's') {
            s = (char*)*ap;
            ap++;
            if(s == 0)
                s = "(null)";
            while(*s != 0){
                putc(fd, *s);
                s++;
            }
        }
    }
}
于 2020-05-14T15:17:21.957 回答