C:尝试将字符串文字 "abc" 分配给大小为 3 的数组,valgrind 检测到错误

C: try to assign string literal "abc" to an array of size 3, valgrind detects error

我一直在想如果我将一个较长的字符串文字分配给一个较小的 char 数组会发生什么。 (我知道如果我使用字符串文字作为初始值设定项,我可能会忽略大小并让编译器计算字符数,或者使用 strlen()+1 作为大小。)

我有以下代码:

#include <stdio.h>

int main() {
    char a[3] = "abc"; // a[2] gives an error of initializer-string for array of chars is too long
    printf("%s\n", a);
    printf("%p\n", a);
}

我预计它会崩溃,但它实际上可以在没有警告的情况下编译并可以打印出来。但是使用 valgrind,我收到以下错误消息。

==19195== Memcheck, a memory error detector
==19195== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==19195== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==19195== Command: ./a.out
==19195== 
==19195== Conditional jump or move depends on uninitialised value(s)
==19195==    at 0x4E88CC0: vfprintf (vfprintf.c:1632)
==19195==    by 0x4E8F898: printf (printf.c:33)
==19195==    by 0x4005CC: main (main.c:5)
==19195== 
==19195== Conditional jump or move depends on uninitialised value(s)
==19195==    at 0x4EB475D: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:850)
==19195==    by 0x4EB56AF: _IO_default_xsputn (genops.c:455)
==19195==    by 0x4EB32C6: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1352)
==19195==    by 0x4E8850A: vfprintf (vfprintf.c:1632)
==19195==    by 0x4E8F898: printf (printf.c:33)
==19195==    by 0x4005CC: main (main.c:5)
==19195== 
==19195== Conditional jump or move depends on uninitialised value(s)
==19195==    at 0x4EB478A: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:858)
==19195==    by 0x4EB56AF: _IO_default_xsputn (genops.c:455)
==19195==    by 0x4EB32C6: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1352)
==19195==    by 0x4E8850A: vfprintf (vfprintf.c:1632)
==19195==    by 0x4E8F898: printf (printf.c:33)
==19195==    by 0x4005CC: main (main.c:5)
==19195== 
==19195== Conditional jump or move depends on uninitialised value(s)
==19195==    at 0x4EB56B3: _IO_default_xsputn (genops.c:455)
==19195==    by 0x4EB32C6: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1352)
==19195==    by 0x4E8850A: vfprintf (vfprintf.c:1632)
==19195==    by 0x4E8F898: printf (printf.c:33)
==19195==    by 0x4005CC: main (main.c:5)
==19195== 
==19195== Syscall param write(buf) points to uninitialised byte(s)
==19195==    at 0x4F306E0: __write_nocancel (syscall-template.S:84)
==19195==    by 0x4EB2BFE: _IO_file_write@@GLIBC_2.2.5 (fileops.c:1263)
==19195==    by 0x4EB4408: new_do_write (fileops.c:518)
==19195==    by 0x4EB4408: _IO_do_write@@GLIBC_2.2.5 (fileops.c:494)
==19195==    by 0x4EB347C: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1331)
==19195==    by 0x4E8792C: vfprintf (vfprintf.c:1663)
==19195==    by 0x4E8F898: printf (printf.c:33)
==19195==    by 0x4005CC: main (main.c:5)
==19195==  Address 0x5203043 is 3 bytes inside a block of size 1,024 alloc'd
==19195==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==19195==    by 0x4EA71D4: _IO_file_doallocate (filedoalloc.c:127)
==19195==    by 0x4EB5593: _IO_doallocbuf (genops.c:398)
==19195==    by 0x4EB48F7: _IO_file_overflow@@GLIBC_2.2.5 (fileops.c:820)
==19195==    by 0x4EB328C: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1331)
==19195==    by 0x4E8850A: vfprintf (vfprintf.c:1632)
==19195==    by 0x4E8F898: printf (printf.c:33)
==19195==    by 0x4005CC: main (main.c:5)
==19195== 
abc?
0xfff0003f0
==19195== 
==19195== HEAP SUMMARY:
==19195==     in use at exit: 0 bytes in 0 blocks
==19195==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==19195== 
==19195== All heap blocks were freed -- no leaks are possible
==19195== 
==19195== For counts of detected and suppressed errors, rerun with: -v
==19195== Use --track-origins=yes to see where uninitialised values come from
==19195== ERROR SUMMARY: 10 errors from 5 contexts (suppressed: 0 from 0)

我认为未初始化的 value/byte 部分是有意义的,因为没有为终止字符 '\0' 分配内存,当我打印出来时,最后一个字符是垃圾值。

但是最后一条错误消息我觉得很陌生。

Address 0x5203043 is 3 bytes inside a block of size 1,024 alloc'd

我知道缓冲区大小被定义为1024。我不确定这个错误是不是因为内存使用效率低下。

我还想知道堆分配和释放是从哪里来的?那是来自字符串文字吗?

感谢您的帮助!!

(这​​道题前面的题目可能措辞混乱,我改了。)

A similar question, but in C++

assigning string literal “abc” to an array of size 3 causes valgrind error

分配不会导致 valgrind 错误。 char a[3] = "abc" 没问题。 C 允许初始化字符数组 sans 空字符。

Successive bytes of the string literal (including the terminating null character if there is room or if the array is of unknown size) initialize the elements of the array. C11 §6.7.9 14

printf("%s", ... 需要一个指向空字符终止数组的指针。 a 不是因为它缺少空字符。代码正在尝试访问 a[] 之外的内容,并且是 未定义的行为 ,错误由此而来。它不是"inefficient use of memory.",而是越界访问未初始化的内存。

而是使用以下打印直到找到空字符或打印 3 个字符。

printf("%.3s\n", a);
// or 
printf("%.*s\n", (int) sizeof a, a);

这是我对正在发生的事情的解释:

您正在写入 stdout,默认情况下是缓冲的。因此,所有数据首先进入内部缓冲区,然后写入 ("flushed") 实际的底层文件描述符。

您的 a 数组不是有效的字符串,因为它缺少终止 NUL 字节。前几条消息来自 printf 内部,它试图通过查找终止符来计算参数字符串的长度,并将内容复制到 stdout 的缓冲区中。由于 a 内没有终止符,代码越界,读取未初始化的内存。

此时输出缓冲区看起来像:

char *buf = malloc(1024), contents:
a b c ? ? ? ?
^^^^^ ^^^^^^^

第一部分 (abc) 合法复制自 a。下一部分是随机垃圾(a 后未初始化的字节,复制到缓冲区中)。这一直持续到 NUL 字节恰好出现在 a 之后的某处,然后将其视为字符串的结尾(这是从 a 停止复制的地方)。

最后是来自格式字符串的 '\n',它也被添加到缓冲区中:

char *buf = malloc(1024), contents:
a b c ? ? ? ? \n
^^^^^ ^^^^^^^ ^^

然后(因为我们遇到了 '\n' 并且 stdout 是行缓冲的)我们刷新缓冲区,调用 write(STDOUT_FILENO, buf, N) 其中 N 是但是很多字节正在使用在输出缓冲区中(这至少是 4,但确切的数字取决于在 a 之后发现 '[=29=]' 之前复制了多少垃圾字节)。

现在,错误:

==19195== Syscall param write(buf) points to uninitialised byte(s)

这是说 write(缓冲区)的第一个参数中有未初始化的字节。

显然 valgrind 将部分输出缓冲区视为未初始化,因为源数据未初始化。将垃圾从 A 复制到 B 意味着 B 也是垃圾。

==19195==  Address 0x5203043 is 3 bytes inside a block of size 1,024 alloc'd

所以它说有一个动态分配的缓冲区(大小为 1024),并且在偏移量 3 处发现了先前错误的 uninitialised byte(s)。这是有道理的,因为偏移量 0、1、2 包含 "abc",这是完全有效的数据。但在那之后就是麻烦的开始。

这也是说该块来自 malloc,它是从 printf(间接)调用的。这是因为 stdout 的输出缓冲区是按需创建的,在您第一次写入它时。这是您 main 中的第一个 printf 调用。