C snprintf для добавления члена структуры char * - PullRequest
0 голосов
/ 09 мая 2018

Когда я распечатываю результат-> значение, я получаю мусор (текст из других областей памяти) и свободный (): неверный указатель

Я пытаюсь безопасно добавить строку в существующий символ * (значение элемента структуры, результатом которого является экземпляр)

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 (или варианта), в то же время следя за тем, чтобы мы не выходили за пределы?

В идеале, это не будет включать временные переменные, другие конструкции, которым нужно более одной строки, так как мне нужно повторить это добавление в нескольких местах.

Ответы [ 2 ]

0 голосов
/ 10 мая 2018

Это неопределенное поведение, поскольку входные аргументы не могут быть частью выходного буфера (как в случае snprintf, так и sprintf):

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

Это указано телеграфно в стандарте C:

& sect; 7.21.6.5/para 2: & hellip; Если копирование происходит между перекрывающимися объектами, поведение не определено. (то же самое предложение встречается в разделе 7.21.6.6/2 в отношении sprintf)

, а также в man sprintf:

& hellip; результаты не определены, если вызов 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;
0 голосов
/ 09 мая 2018

Вот некоторые проблемы с вашим кодом. Тип Bool должен быть определен, а также False. Данные, на которые указывает pointer, не инициализированы. При вызове sprintf вы читаете и пишете result->value, что является неопределенным поведением.

Вот полная рабочая реализация, без неопределенного поведения, где значение result->name читается, а результат snprintf записывается в result->value:

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

...