【问题标题】:C: try to assign string literal "abc" to an array of size 3, valgrind detects errorC:尝试将字符串文字“abc”分配给大小为 3 的数组,valgrind 检测到错误
【发布时间】:2017-03-02 16:05:58
【问题描述】:

我一直在想,如果我将较长的字符串文字分配给较小大小的 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)

我认为未初始化的值/字节部分是有道理的,因为没有为终止字符“\0”分配内存,当我打印出来时,最后一个字符是垃圾值。

但最后一条错误消息对我来说似乎很陌生。

地址 0x5203043 在大小为 1,024 的块中分配了 3 个字节

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

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

感谢您的帮助!!

(这个问题的前一个主题可能措辞混乱。我改变了它。)

A similar question, but in C++

【问题讨论】:

标签: c arrays memory valgrind string-literals


【解决方案1】:

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

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

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

此时输出缓冲区将如下所示:

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

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

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

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

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

现在,错误:

==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 呼叫。

【讨论】:

    【解决方案2】:

    将字符串文字“abc”分配给大小为 3 的数组会导致 valgrind 错误

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

    字符串文字的连续字节(包括 如果有空间或数组大小未知,则终止空字符)初始化数组的元素。 C11 §6.7.9 14

    printf("%s", ... 需要一个指向空字符终止数组的指针。 a 并非如此,因为它缺少空字符。代码试图访问a[] 之外的内容,并且是未定义的行为,错误由此而来。不是“内存使用效率低”,而是越界访问未初始化的内存。

    改为使用以下内容,直到找到空字符或打印 3 个字符。

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

    【讨论】:

    • 没有什么比无缘无故投反对票更糟糕的了——尤其是在答案正确的情况下。
    • @KevinDTimm 同意,带 DV 的评论更有用。然而,正如我不会无缘无故地抱怨紫外线,我也不会无缘无故地抱怨 DV。
    • 问题是 valgrind 错误消息的含义,特别是 Address 0x5203043 is 3 bytes inside a block of size 1,024 alloc'd。就 C 而言,这个答案是正确的,但它并不试图回答这个问题。
    • @melpomene - 问题的主题是:“C:将字符串文字“abc”分配给大小为 3 的数组会导致 valgrind 错误”。 chux 已经证实这不是真的(问题的答案也是如此)
    • @KevinDTimm 主题不是问题本身,它确实会导致 valgrind 错误,至少是间接的(因为代码在其上调用 printf 并尝试将数组用作字符串)。好的,所以标题有点不准确。但是,如果您将问题读到最后,您会发现这完全是为了解释最后一条 valgrind 消息。
    猜你喜欢
    • 1970-01-01
    • 2012-05-03
    • 2018-04-09
    • 1970-01-01
    • 2014-03-17
    • 2012-02-19
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    相关资源
    最近更新 更多