【问题标题】:strdup invalid read of size 4 when string literal is ending with newline \n当字符串文字以换行符结尾时,strdup 读取大小为 4 的无效 \n
【发布时间】:2016-05-03 11:32:24
【问题描述】:

当 src 字符串以 \n 结尾时出现无效读取错误,删除 \n 后错误消失:

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

int main (void)
{
    char *txt = strdup ("this is a not socket terminated message\n");
    printf ("%d: %s\n", strlen (txt), txt);
    free (txt);
    return 0;
}

valgrind 输出:

==18929== HEAP SUMMARY:
==18929==     in use at exit: 0 bytes in 0 blocks
==18929==   total heap usage: 2 allocs, 2 frees, 84 bytes allocated
==18929== 
==18929== All heap blocks were freed -- no leaks are possible
==18929== 
==18929== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
==18929== 
==18929== 1 errors in context 1 of 1:
==18929== Invalid read of size 4
==18929==    at 0x804847E: main (in /tmp/test)
==18929==  Address 0x4204050 is 40 bytes inside a block of size 41 alloc'd
==18929==    at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==18929==    by 0x8048415: main (in /tmp/test)
==18929== 
==18929== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

如何在不牺牲换行符的情况下解决这个问题?

【问题讨论】:

  • 确定strdup() 是原型。将"%zu" 用于strlen()'\n' 不太可能是问题所在。
  • 在 MSVC 上没有正确的头文件的问题。他们在哪里?
  • 在 MSVC 上一切皆有可能,这不是 C 的正确参考
  • 问题很可能是格式说明符。使用 %lu 而不是 %d。如果您使用 gcc,请打开 -Wformat。
  • @razzak 使用 C90,将结果转换为最广泛的可用无符号类型,至少为 unsigned long: printf ("%lu\n", (unsigned long) strlen(txt));

标签: c strdup


【解决方案1】:

错误消息似乎表明是strlen 读取了malloced 由strdup 分配的缓冲区。在 32 位平台上,最佳的strlen 实现可以一次将 4 个字节读取到 32 位寄存器中并进行一些位旋转以查看其中是否有空字节。如果在字符串末尾附近,剩余的字节数少于 4 个,但仍会读取 4 个字节以执行空字节检查,那么我可以看到此错误被打印出来。在这种情况下,大概strlen 实现者会知道在特定平台上执行此操作是否“安全”,在这种情况下,valgrind 错误是误报。

【讨论】:

    【解决方案2】:

    这与换行符无关,也与 printf 格式说明符无关。你在strlen() 中发现了一个可以说是错误的地方,我可以告诉你一定是在使用 gcc。

    您的程序代码非常好。 printf 格式说明符可能会好一些,但不会导致您看到的 valgrind 错误。让我们看看那个 valgrind 错误:

    ==18929== Invalid read of size 4
    ==18929==    at 0x804847E: main (in /tmp/test)
    ==18929==  Address 0x4204050 is 40 bytes inside a block of size 41 alloc'd
    ==18929==    at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
    ==18929==    by 0x8048415: main (in /tmp/test)
    

    “Invalid read of size 4”是我们必须理解的第一条消息。这意味着处理器运行了一条指令,该指令将从内存中加载 4 个连续字节。下一行表示尝试读取的地址是“地址 0x4204050 在大小为 41 的块内分配了 40 个字节。”

    有了这些信息,我们就可以弄清楚了。首先,如果将 '\n' 替换为 '$' 或任何其他字符,则会产生相同的错误。试试看。

    其次,我们可以看到您的字符串中有 40 个字符。添加 \0 终止字符会使用于表示字符串的总字节数达到 41。

    因为我们有消息“地址 0x4204050 在一个大小为 41 的块中分配了 40 个字节”,所以我们现在知道出了什么问题。

    1. strdup() 分配了正确的内存量,41 字节。
    2. strlen() 尝试从第 40 个字节开始读取 4 个字节,这将扩展到不存在的第 43 个字节。
    3. valgrind 发现了问题

    这是一个 glib() 错误。曾几何时,一个名为 Tiny C Compiler (TCC) 的项目开始兴起。巧合的是,glib 完全改变了,以至于普通的字符串函数,如strlen() 不再存在。它们被优化版本所取代,这些版本使用各种方法读取内存,例如一次读取四个字节。同时更改了 gcc 以生成对适当实现的调用,具体取决于输入指针的对齐方式、编译的硬件等。当对 GNU 环境的这种更改使得难以生成新的 C 编译器,取消了对标准库使用 glib 的能力。

    如果您报告错误,glib 维护人员可能不会修复它。原因是在实际使用中,这可能永远不会导致实际崩溃。 strlen 函数一次读取 4 个字节,因为它发现地址是 4 字节对齐的。假设从该地址读取 1 个字节会成功,则始终可以从 4 字节对齐的地址读取 4 个字节而不会出现段错误。因此,来自 valgrind 的警告并没有揭示潜在的崩溃,只是关于如何编程的假设不匹配。我认为 valgrind 在技术上是正确的,但我认为 glib 维护者会做任何事情来压制警告的可能性为零。

    【讨论】:

    • 我可以确认用任何其他字符替换 \n 会产生相同的错误,我也在使用 gcc。感谢您的冗长解释,现在一切都说得通了。
    • 不错的答案,但他给出的输出与代码无关。在输出中,他向我们展示了这个total heap usage: 2 allocs, 2 frees, 84 bytes allocatedalloc 在哪里出现两次?
    • @Michi - 可能在 printf 内部。
    • @Michi 可能在 glib 启动代码中:crt0.c。总之,没什么大不了的。
    • @razzak - 在这里 (bugzilla.redhat.com/show_bug.cgi?id=678518) 看起来好像没有 valgrind 解决方法,但是使用 -fno-builtin-strlen 编译会生成更慢的代码,但不会触发此警告。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多