我有一个void *
,称之为data
,我知道它的长度,但不会终止。我拨打了这样的电话snprintf(line, sizeof(line), "%*s", n, (const char*)data)
,其中n
是已知的长度。几乎总是这样,但偶尔会导致段错误。
每当发生段错误时,后面跟踪表明问题出在strlen内部。当我在gdb中打印data
时,我看到类似这样的内容
(gdb) p n
$1 = 88
(gdb) p (const char*) data
$2 = 0x1d752fa8
"JASDF" ... "ADS"<Address 0x1d753000 out of bounds>
(gdb) p 0x1d753000-0x1d752fa8
$3 = 88
data
确实是88个字符,但不是空终止,事实上,它似乎正好对着一个段。我的猜测是snprintf在数据上总是被称为strlen,我通常很幸运,因为即使data
没有空终止,在我点击该段之前还有一个\0
然后我偶尔会感到不幸它是。是对的吗?如果是这样,那有什么作用?
这就是堆栈跟踪的样子
#0 0x0000003c8927839e in strlen () from /lib64/libc.so.6
#1 0x0000003c89246749 in vfprintf () from /lib64/libc.so.6
#2 0x0000003c8926941a in vsnprintf () from /lib64/libc.so.6
#3 0x0000003c8924d0a3 in snprintf () from /lib64/libc.so.6
编辑要回答我自己关于解决方法的问题,strncpy是一个更适合调用的函数。我习惯使用snprintf。
答案 0 :(得分:7)
snprintf(line, sizeof(line), "%*s", n, (const char*)data)
data
不是零终止?那你做错了。
snprintf(line, sizeof(line), "%.*s", n, (const char*)data);
注意点。
如果是字符串,*
中的第一个*.*
如果是所需的输出(屏幕上)长度 - 输入长度是第二个*
。 man printf
了解更多信息。
显然在%*s
格式化的情况下可能会调用strlen(),因为它需要知道是否需要填充输出以及如何填充它。
答案 1 :(得分:6)
看起来你是对的。我无法保证printf
不会调用strlen
,即使它不一定要在给定的上下文中。你是通过提供一个非C字符串作为%s
格式说明符的参数而撒谎,所以你违反了printf
的契约。未定义的行为结果。
答案 2 :(得分:1)
我认为你的分析是正确的。如果缓冲区未终止null,则strlen调用将一直读取,直到找到\0
。如果它超出了段的末尾(并且下一段无效),那么它将产生异常。
解决方案是将其终止或将其放入另一个可以null终止的缓冲区中。
答案 3 :(得分:0)
“解决方法”是使用memcpy()
:
size_t validlen = n < sizeof line ? n : (sizeof line - 1);
memcpy(line, data, validlen);
line[validlen] = '\0';