繁体   English   中英

C snprintf追加结构成员char *

[英]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(或变体)并确保我们不会超出范围的正确方法是什么?

理想情况下,它不会涉及需要多于一行的其他临时变量,因为我需要在多个位置重复此追加。

这是未定义的行为,因为输入参数不能是输出缓冲区的一部分(对于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() vsprintf()将导致在重叠的对象之间进行复制,则结果是不确定的(例如,如果目标字符串数组和提供的输入参数之一)指相同的缓冲区)。 (Linux版本)

如果在某些情况下碰巧产生了预期的结果,那么您很幸运(或者很不幸,因为the幸可能会导致您得出无效的结论)。

尽管这是正确的,但此行的作用却非常复杂:

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;

这是您的代码中的一些问题。 应该定义类型Bool应该定义False pointer数据未初始化。 在对sprintf的调用中,您读写了result->value ,这是未定义的行为。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM