2

当 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)

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

4

2 回答 2

8

这与换行符无关,也与 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)

“无效读取大小 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 维护者会做任何事情来压制警告的可能性为零。

于 2016-01-26T20:27:50.123 回答
3

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

于 2016-01-26T20:04:13.763 回答