C snprintf追加struct member char *
当我打印出result-> value时,我得到垃圾(来自其他内存区域的文本)和free():无效指针
我试图安全地将一个字符串附加到现有的char *(结果的值成员,其结果是一个实例)
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/第2段:……如果在重叠的对象之间进行复制,则行为未定义。 (对于
sprintf
,相同的句子出现在§7.21.6.6/ 2中)
而且在man sprintf
:
…如果调用
sprintf()
,snprintf()
,vsprintf()
或vsnprintf()
会导致在重叠的对象之间发生复制(例如,如果目标字符串数组和提供的输入参数之一sprintf()
,则结果是未定义的指的是同一个缓冲区)。 (Linux版)
如果它碰巧在某些情况下产生了预期的结果,那么你很幸运(或不幸,因为侥幸可能导致你得出无效的结论)。
虽然它不是不正确的,但这条线的function非常复杂:
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
: