[英]C snprintf to append struct member char*
当我打印出result-> value时,我得到了垃圾(其他内存区域的文本)和一个free():无效的指针
我试图将字符串安全地附加到现有char *(结果为实例的结构的value成员)上
unsigned const NAME_BUFFER=100;
unsigned const VALUE_BUFFER=1000;
typedef struct {
char *name;
int ID;
char *value;
} props;
…
static Bool
myfunc(props *result)
{
unsigned char *pointer;
result->name=malloc(NAME_BUFFER);
result->value=malloc(VALUE_BUFFER);
// not sure I need to do this, previous instances of the code produced
// output such as the quick...(null)someoutput
// which I thought might be because the member is empty the first time?
sprintf(result->name,"%s","\0");
sprintf(result->value,"%s","\0");
…
// in a loop which sets pointer, we want to safely append the value of pointer to the
// value of the member called value
snprintf(result->value,VALUE_BUFFER,"the quick...%s%s",result->value,pointer);
…
return False;
}
static void
my_print_func()
{
props *result=malloc(sizeof(props));
if(my_func(result))
printf("%d\t%s",result->ID,result->value);
}
更改以上内容以使用sprintf不会导致这些问题:
sprintf(result->value,"the quick...%s%s",result->value,pointer);
...事实是它会很乐意尝试插入比分配的字符更多的字符。
那么添加sprintf(或变体)并确保我们不会超出范围的正确方法是什么?
理想情况下,它不会涉及需要多于一行的其他临时变量,因为我需要在多个位置重复此追加。
这是未定义的行为,因为输入参数不能是输出缓冲区的一部分(对于snprintf
和sprintf
):
snprintf(result->value,VALUE_BUFFER,"the quick...%s%s",result->value,pointer);
这在C标准中以电报方式指定:
§7.21.6.5/ para 2:…如果在重叠的对象之间进行复制,则行为是不确定的。 (关于
sprintf
,同一句出现在§7.21.6.6/ 2中)
并且在man sprintf
:
…如果对
sprintf()
,snprintf()
,vsprintf()
或vsnprintf()
vsprintf()
将导致在重叠的对象之间进行复制,则结果是不确定的(例如,如果目标字符串数组和提供的输入参数之一)指相同的缓冲区)。 (Linux版本)
如果在某些情况下碰巧产生了预期的结果,那么您很幸运(或者很不幸,因为the幸可能会导致您得出无效的结论)。
尽管这是正确的,但此行的作用却非常复杂:
sprintf(result->name,"%s","\0");
"\\0"
被视为零长度的字符串,因为字符串以第一个NUL字符终止,因此它与""
的区别仅在于它使用了两个字节而不是一个字节。 但是无论如何,您都可以简单地写:
result->name[0] = 0; /* Or ... `\0` if you like typing */
标准库包括用于连接字符串的strcat
和strncat
,但是“安全”版本strncat
仅允许您指定要附加的字符数的限制,而不是字符串总长度的限制。 因此,您需要自己跟踪可用的字符数,如果要这样做,最好也跟踪字符串末尾的位置,也就是要复制附加字符的位置。字符串,而不是每次进行连接时都搜索结尾。 因此, str(n)cat
几乎不是字符串连接的正确解决方案。
这是将多个块连接到输出缓冲区的简单概述:
size_t used = 0;
result->value = malloc(MAX_VALUE_LEN + 1);
for (...) { /* loop which produces the strings to append */
...
/* append a chunk */
size_t chunk_len = strlen(chunk);
if (MAX_VALUE_LEN - used >= chunk_len) {
memcpy(result->value + used, chunk, chunk_len);
used += chunk_len;
}
else {
/* Value is too long; return an error */
}
}
result->value[used] = 0;
并非每个人都会同意我使用memcpy而不是strcpy。 之所以这样做,是因为我已经知道要复制的字符串的长度(为了检查是否有足够的空间,我必须弄清楚该字符串的长度),并且复制已知数量的字节通常比复制字节要有效得多,直到复制完为止你打了NUL。
使用memcpy
强制我显式NUL终止结果,但是否则我将不得不在开始时插入NUL,以防循环无法添加任何内容。 为了给NUL留出空间,我最初分配了MAX_VALUE_LEN + 1
个字节。 然而,在实践中我可能会先小分配和成倍realloc
如果必要的话,而不是强加的人为限制,在通常情况下,该人为限制比实际需要的内存更大的浪费内存。
如果大小限制不是人为限制的-也就是说,如果存在某些限制附加字符串长度的外部性(例如输出显示框的大小),则可以选择简单地截断字符串,而不是抛出超尺寸结果错误:
size_t used = 0;
result->value = malloc(MAX_VALUE_LEN + 1);
for (...) { /* loop which produces the strings to append */
...
/* append a chunk */
size_t chunk_len = strlen(chunk);
if (MAX_VALUE_LEN - used < chunk_len) {
chunk_len = MAX_VALUE_LEN - used;
}
memcpy(result->value + used, chunk, chunk_len);
used += chunk_len;
}
result->value[used] = 0;
这是您的代码中的一些问题。 应该定义类型Bool
应该定义False
。 pointer
数据未初始化。 在对sprintf
的调用中,您读写了result->value
,这是未定义的行为。
这是一个完整的工作实现,没有未定义的行为,其中读取result->name
的值,并将snprintf
结果写入result->value
:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.