当我打印出result->值时,我得到垃圾(来自其他内存区域的文本)和free():无效指针
我正在尝试安全地将字符串附加到现有的char *(结果的值成员,其结果是实例)
db.bank.aggregate([{"$match":{"_id.@objectName":"AccountBalance"}},{"$unwind":{"path":"$Versions"}},{"$sort":{"Versions.version":-1}},{"$group":{"_id":{"accno":"$_id.AccountNumber","branchid":"$_id.branchId"},"value":{"$first":"$Versions.value"}}},{"$group":{"_id":"$_id.branchid","total":{"$sum":"$value"}}}])
更改以上内容以使用sprintf不会导致这些问题:
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(或变体)附加的正确方法是什么,同时确保我们不会超出界限?
理想情况下,它不会涉及临时变量需要多行的其他构造,因为我需要在几个地方重复这个追加。
答案 0 :(得分:1)
这是未定义的行为,因为输入参数不能是输出缓冲区的一部分(在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()
会导致重叠的对象之间发生复制(例如,如果目标字符串数组和提供的输入参数之一引用相同的缓冲区。 (Linux版)
如果碰巧在某些情况下产生了预期的结果,那么你很幸运(或不幸,因为侥幸可能导致你得出无效的结论)。
虽然这不是不正确的,但这条线的功能非常复杂:
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;
答案 1 :(得分:-1)
以下是您的代码的一些问题。应定义类型Bool
,并定义False
。 pointer
指向的数据未初始化。在您对sprintf
的调用中,您读取并写入result->value
这是一种未定义的行为。
这是一个完整的工作实现,没有未定义的行为,其中result->name
的值被读取,snprintf
的结果被写入result->value
: