简体   繁体   English

C snprintf追加结构成员char *

[英]C snprintf to append struct member char*

When I print out result->value I get garbage (text from other memory areas), and a free(): invalid pointer 当我打印出result-> value时,我得到了垃圾(其他内存区域的文本)和一个free():无效的指针

I am trying to safely append a string to an existing char* (the value member of the struct of which result is an instance) 我试图将字符串安全地附加到现有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);
}

Changing the above to use sprintf doesn't cause these problems: 更改以上内容以使用sprintf不会导致这些问题:

sprintf(result->value,"the quick...%s%s",result->value,pointer);

... other than the fact that it would happily try to insert more characters than allocated. ...事实是它会很乐意尝试插入比分配的字符更多的字符。

So what is the correct way of appending with sprintf (or variant) whilst also making sure we don't go out of bounds? 那么添加sprintf(或变体)并确保我们不会超出范围的正确方法是什么?

Ideally, it wouldn't involve temporary variables other constructs that need more than one line as I need to repeat this append in several places. 理想情况下,它不会涉及需要多于一行的其他临时变量,因为我需要在多个位置重复此追加。

This is undefined behaviour because the input arguments cannot be part of the output buffer (both in the case of snprintf and sprintf ): 这是未定义的行为,因为输入参数不能是输出缓冲区的一部分(对于snprintfsprintf ):

snprintf(result->value,VALUE_BUFFER,"the quick...%s%s",result->value,pointer);

This is specified telegraphically in the C standard: 这在C标准中以电报方式指定:

§7.21.6.5/para 2: …If copying takes place between objects that overlap, the behavior is undefined. §7.21.6.5/ para 2:…如果在重叠的对象之间进行复制,则行为是不确定的。 (the same sentence appears in §7.21.6.6/2 with respect to sprintf ) (关于sprintf ,同一句出现在§7.21.6.6/ 2中)

and also in man sprintf : 并且在man sprintf

…the results are undefined if a call to sprintf() , snprintf() , vsprintf() , or vsnprintf() would cause copying to take place between objects that overlap (eg, if the target string array and one of the supplied input arguments refer to the same buffer). …如果对sprintf()snprintf()vsprintf()vsnprintf() vsprintf()将导致在重叠的对象之间进行复制,则结果是不确定的(例如,如果目标字符串数组和提供的输入参数之一)指相同的缓冲区)。 (Linux version) (Linux版本)

If it happens to produce the expected result in some circumstances, you were lucky (or unlucky, since the fluke could lead you to an invalid conclusion). 如果在某些情况下碰巧产生了预期的结果,那么您很幸运(或者很不幸,因为the幸可能会导致您得出无效的结论)。

Although it is not incorrect, this line is ridiculously complicated for what it does: 尽管这是正确的,但此行的作用却非常复杂:

sprintf(result->name,"%s","\0");

"\\0" is treated as a zero-length string because strings are terminated by the first NUL character, so it only differs from "" by the fact that it uses up two bytes instead of one. "\\0"被视为零长度的字符串,因为字符串以第一个NUL字符终止,因此它与""的区别仅在于它使用了两个字节而不是一个字节。 But in any case, you could simply write: 但是无论如何,您都可以简单地写:

result->name[0] = 0; /* Or ... `\0` if you like typing */

The standard library includes strcat and strncat for concatenating strings, but the "safe" version strncat only lets you specify a limit to the number of characters to append, not a limit to the total length of the string. 标准库包括用于连接字符串的strcatstrncat ,但是“安全”版本strncat仅允许您指定要附加的字符数的限制,而不是字符串总长度的限制。 So you need to keep track of the number of characters available yourself, and if you're going to do that, you might as well instead keep track of the position of the end of the string, which is where you want to copy the appended string, rather than searching for the end every time you do a concatenation. 因此,您需要自己跟踪可用的字符数,如果要这样做,最好也跟踪字符串末尾的位置,也就是要复制附加字符的位置。字符串,而不是每次进行连接时都搜索结尾。 For this reason, str(n)cat are hardly ever the correct solution for string concatenation. 因此, str(n)cat几乎不是字符串连接的正确解决方案。

Here's a simple outline for concatenating multiple chunks to an output buffer: 这是将多个块连接到输出缓冲区的简单概述:

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;

Not everyone will agree with my use of memcpy rather than strcpy; 并非每个人都会同意我使用memcpy而不是strcpy。 I did it because I already knew the length of the string to copy (which I had to figure out in order to check whether there was enough space), and it is usually more efficient to copy a known number of bytes than to copy bytes until you hit a NUL. 之所以这样做,是因为我已经知道要复制的字符串的长度(为了检查是否有足够的空间,我必须弄清楚该字符串的长度),并且复制已知数量的字节通常比复制字节要有效得多,直到复制完为止你打了NUL。

The use of memcpy forces me to explicitly NUL-terminate the result, but I would otherwise have had to insert a NUL at the beginning in case the loop didn't manage to append anything. 使用memcpy强制我显式NUL终止结果,但是否则我将不得不在开始时插入NUL,以防循环无法添加任何内容。 In order to leave room for the NUL, I initially allocated MAX_VALUE_LEN + 1 bytes. 为了给NUL留出空间,我最初分配了MAX_VALUE_LEN + 1个字节。 However, in practice I would probably start with a small allocation and exponentially realloc if necessary, rather than imposing an artificial limit and wasting memory in the common case that the artificial limit was much greater than the memory actually needed. 然而,在实践中我可能会先小分配和成倍realloc如果必要的话,而不是强加的人为限制,在通常情况下,该人为限制比实际需要的内存更大的浪费内存。

If the size limit is not artificial -- that is, if there is some externality which constrains the length of the appended string, such as the size of an output display box -- then one might choose to simply truncate the string rather than throwing an error for over-size results: 如果大小限制不是人为限制的-也就是说,如果存在某些限制附加字符串长度的外部性(例如输出显示框的大小),则可以选择简单地截断字符串,而不是抛出超尺寸结果错误:

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;

Here are some issues with your code. 这是您的代码中的一些问题。 Type Bool should be defined, and False as well. 应该定义类型Bool应该定义False Data pointed to by pointer is not initialized. pointer数据未初始化。 In your call to sprintf you read and write to result->value which is an undefined behavior. 在对sprintf的调用中,您读写了result->value ,这是未定义的行为。

Here is a full working implementation, without undefined behavior, where the value of result->name is read and result of snprintf is written to result->value : 这是一个完整的工作实现,没有未定义的行为,其中读取result->name的值,并将snprintf结果写入result->value

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

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

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