【问题标题】:how come printf is printing a non null terminated string? [closed]printf 怎么会打印一个非空终止的字符串? [关闭]
【发布时间】:2012-12-01 04:33:18
【问题描述】:

c 编程书说我的字符串必须以 null 结尾才能使用 printf 打印,但以下程序仍会打印字符串,尽管它不是以 null 结尾的!!

#include <stdio.h>
#include <stdlib.h>

int main(){
    int i ;
    char str[10] ;
    for(i = 0 ; i < 10 ; i++ ) {
        str[i] = (char)(i+97) ;
    }

    printf("%s",str) ;
}

我正在使用代码块 IDE。

【问题讨论】:

  • 你碰巧很幸运,在那个数组后面的某个地方有一个0。您刚刚利用了经验丰富的程序员称为“未定义行为”的东西。不要指望在任何地方都能做到这一点,大多数程序都会崩溃。
  • 我已经运行了 1000 次循环,但它总是运行相同而没有任何问题。我应该考虑从 codeblocks 更改我的编译器吗?谢谢。
  • 未定义并不意味着不可重复。尝试使用不同的标志编译,或在不同的架构上编译。
  • 这就像酒驾一样。如果你这样做,你会在大多数时候逃脱。你只是在危及自己和他人。

标签: c


【解决方案1】:

读取超出数组的范围是未定义的行为,实际上你是不走运,如果你运行它足够的次数或调用它,它并没有崩溃它可能(或可能不会)崩溃的函数,您应该始终终止字符串,或使用宽度说明符:

printf("%.10s", str);

【讨论】:

  • 我想我有一个新的疑问,如果我不以空字符终止字符串并使用宽度说明符,那么它永远不会崩溃吗?
  • @NikunjBanka 不,它不会,只要你不超过数组边界就可以了。
【解决方案2】:

str 的第 10 个元素之后的内容恰好为 null。该 null 超出了数组的定义边界,但 C 没有数组边界检查。就你的情况来说,这就是它的结果,这只是运气。

【讨论】:

  • 谢谢,但现在我已经在循环中运行了相同的代码 1000 次,并且每次都可以正常工作。那么现在我应该考虑编译器本身的问题吗?(它会自动放置一个空字符)
  • 每当您发现自己想知道编译器是否损坏时,您都没有认真研究自己的代码或您自己对问题的理解。作为一个新手,总是假设问题出在你身上。
  • #include #include #include int main(){ int i ; for(i = 0 ; i
  • @user189535:所以现在在char str[10] 之后的下一行声明另一个数组。叫它char str2[10]。用“B”或任何你想要的填充它。现在打印 str 看看会发生什么。
  • @NikunjBanka:嗯,从技术上讲,它打印垃圾是幸运的。我的意思是在 before str[10] 行上声明一个数组。取决于您的机器架构。但无论如何,它与局部变量如何在内存中一个接一个地出现有关。因此,当printf 超出您的数组边界进入 la-la 土地时,它最终会在内存中打印另一个变量。但这超出了 C 的范围。C 语言只是说如果你进入 la-la 土地,行为是未定义的。
【解决方案3】:

根据 C 标准,printf 函数打印字符串中的字符,直到找到空字符。否则在定义的数组索引之后它会做什么没有定义。我已经测试了你的代码。并在打印“abcdefghij”后打印一些垃圾值。

【讨论】:

  • 我正在使用 codeblocks IDE,但没有得到任何垃圾值。您使用的是哪个编译器。我应该考虑改变我的编译器吗?谢谢。
  • 您的代码无效。您的代码的行为是未定义的。 C 规范说,超出数组末尾的读取是未定义的行为,这意味着编译器可以使任何事情发生。编译器没有损坏。你的代码是。
【解决方案4】:

如果您在该调用之前执行其他操作,则您的堆栈区域将包含未使用的数据以外的其他数据。想象一下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int use_stack(void) {
   char str[500];
   memset(str, 'X', sizeof(str));
   printf("Filled memory from %p to %p.\n", &str, &str+sizeof str);
}

void print_stuff() {
    int i;
    char str[16]; // changed that so that 10..15 contain X
    for(i = 0; i < 10; i++) {
        str[i] = (char)(i+97);
    }

    printf("%s<END>",str); // have a line break before <END>? Then it comes from i.
    printf("&str: %p\n", &str);
    printf("&i: %p\n", &i);
    // Here you see that i follows str as &i is &str + 16 (0x10 in hex)
}

int main() {
    use_stack();
    print_stuff();
}

您的堆栈区域将充满Xes,printf() 会看到它们。

在您的情况和环境中,堆栈在程序启动时巧合地“干净”。

编辑:这可能会也可能不会发生。如果编译器将变量 i 紧跟在数组之后,那么您的数据仍将是 NUL-终止的,因为第一个字节是 i 的值(您碰巧也打印了它 - 它可能是一个 libne在您的情况下中断-第二个字节是NUL 字节。即使是这种情况,您的代码也会调用UB(未定义的行为)。

如果你的输出包含0A 字符,你能看看(通过管道程序输出到hexdump 或类似的)吗?如果是这样,我的猜测是正确的。我刚刚对其进行了测试,在我的编译器 (gcc) 上似乎是这样。

如前所述,您无需依赖任何东西。

EDIT2:如果您在&lt;END&gt; 之前看到换行符,我的猜测是正确的。如果你看看现在正在打印的指针,你可以比较它们在内存中的地址。

【讨论】:

  • 我不知道 hexdump 是什么,但您提供的代码运行时不会崩溃或打印任何垃圾值。我正在 windows 7 上的代码块 IDE 上运行代码。
  • @NikunjBanka 是的,因为i 在堆栈上跟随str 并且因为str[] 被充分使用。如果你做str[15]str[]的元素10..14将被X填充。
【解决方案5】:

因为在调试模式下,*(str+10) 和整个未使用的空间都有一个初始化值 '0',所以看起来它是 0 终止的。

bash-3.2$ clang -O0 t.c -o t #compile in debug mode
bash-3.2$ ./t
abcdefghij
bash-3.2$ clang -O2 t.c -o t #compile with optimization
bash-3.2$ ./t
abcdefghij2÷d=

【讨论】:

  • 但是NUL 字符应该位于数组后面。所以 str[] 是否初始化为 \0 无关紧要(你认为会发生在哪里?),奇怪的是超出了数组的范围。
  • 我的意思是*(str+10),而不是整个数组。
猜你喜欢
  • 2011-01-09
  • 1970-01-01
  • 2011-04-15
  • 1970-01-01
  • 1970-01-01
  • 2015-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多