C snprintf追加struct member char *

时间:2018-05-09 18:27:05

标签: c struct char printf

当我打印出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(或变体)附加的正确方法是什么,同时确保我们不会超出界限?

理想情况下,它不会涉及临时变量需要多行的其他构造,因为我需要在几个地方重复这个追加。

2 个答案:

答案 0 :(得分:1)

这是未定义的行为,因为输入参数不能是输出缓冲区的一部分(在snprintfsprintf的情况下):

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 */

标准库包含strcatstrncat用于连接字符串,但“安全”版本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,并定义Falsepointer指向的数据未初始化。在您对sprintf的调用中,您读取并写入result->value这是一种未定义的行为。

这是一个完整的工作实现,没有未定义的行为,其中result->name的值被读取,snprintf的结果被写入result->value

https://taas.trust-in-soft.com/tsnippet/t/de28e2a6