tl; dr Можно ли наивно использовать asprintf для конкатенации без вызова временного указателя?


Функция asprintf, введенная GNU и принятая в нескольких других реализациях clib, является заманчивым решением для произвольной конкатенации в c с использованием такой схемы, как

int i=0;
char *str = strdup(argv[i]);
while (argv[++i]) {
   asprintf(&str,"%s %s",argv[i],str);   // <=== This line
}
asprintf(&str,"%s\n",str);

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

Но...

Утечка памяти повсюду? Valgrind говорит, что это на моей коробке. Это ошибка?

На странице руководства передо мной написано:

Функции asprintf () и vasprintf () устанавливают * ret как указатель на буфер, достаточно большой для хранения форматированной строки. Этот указатель следует передать в free (3), чтобы освободить выделенную память, когда она больше не нужна. Если не удается выделить достаточно места, asprintf () и vasprintf () вернут -1 и установят в ret указатель NULL.

В отсутствие фразы "установить *ret как указатель на новый буфер [...]" , я склонен предположить, что функция использует realloc, как getline.

Что может быть проблемой?

Использование подписи int asprintf(char **ret, const char *format, ...); для конкретности.

  1. asprintf запускает realloc слишком рано.

    Представьте, что мы реализуем функцию таким образом, чтобы можно было запустить realloc(*ret) до того, как он разыменует один из переменных, использовавших псевдоним исходного буфера. Этот буфер был освобожден, и это технически неопределенное поведение. Это было бы ошибкой.

  2. asprintf записывает в буфер перед его чтением. В приведенном выше коде мы можем представить функцию, копирующую содержимое argv[1] в *ret перед каждым va_arg аргумента str. Цитируемая мной страница руководства, похоже, не исключает этого случая.

  3. asprintf не free *ret ни напрямую, ни с использованием realloc. Это позволит избежать проблемы номер (1), но приведет к утечке памяти при использовании, как указано выше. Опять же, эта страница руководства, похоже, не исключает этого.

Работать вокруг

Всего вышеперечисленного можно было бы избежать, заменив единственный вызов asprintf на

{
  char *newStr = NULL;
  asprintf(newStr,"%s %s",argv[i],str);
  free(str);
  str = newStr;
}

Но это довольно неуклюже.

Гарантирует ли согласованная реализация безопасность и правильность первого образца кода?

6
dmckee --- ex-moderator kitten 26 Мар 2013 в 22:46
Кстати, хороший вопрос. +1.
 – 
user529758
26 Мар 2013 в 23:00
2
asprintf - это не стандартная функция, это расширение GNU. "Это утечка памяти повсюду?" -- Конечно, это является. Док говорит освободить память, но вы этого не делаете. asprintf, возможно, не может realloc его первый аргумент - это выходной параметр, и его содержание нельзя предполагать.
 – 
Jim Balter
26 Мар 2013 в 23:03
Э ... да. Верно. Думаю, они также находятся в библиотеке BSD, которую сейчас использует Mac OS. Последующие правки.
 – 
dmckee --- ex-moderator kitten
26 Мар 2013 в 23:05
Если вы передаете непустой указатель на asprintf, он перезаписывается вызовом malloc. Он не использует предварительно выделенную память.
 – 
teppic
26 Мар 2013 в 23:07
«Я склонен предположить, что функция использует realloc, как и getline». - Делать такое предположение совершенно неверно, потому что в документации getline подробно описано, как используется значение аргумента lineptr. Просто сравните документацию, и очевидно, что asprintf не работает таким же образом ... он никогда не использует предыдущее значение *ret.
 – 
Jim Balter
26 Мар 2013 в 23:19

1 ответ

Лучший ответ

Утечка памяти повсюду?

Да.

char *str = strdup(argv[i]);

Здесь str содержит указатель на malloc() выделенную память, которая должна быть free() d.

asprintf(&str, "%s %s", argv[i], str);

Здесь asprintf() изменяет str так, чтобы он указывал на некоторую другую память, выделенную самой функцией. Теперь вы только что потеряли указатель на строку ped strdup(), отсюда и утечка.

3
user529758user529758 26 Мар 2013 в 22:51
1
Это зависит от погоды, asprintf использует realloc (как, например, getline). Из справочной страницы неясно, нужно или нет. Простое добавление "новой" работы в процитированном выше абзаце сделало бы это очень ясным, но без этого ...
 – 
dmckee --- ex-moderator kitten
26 Мар 2013 в 22:55
Ближайшая страница руководства, которую я смог найти, - это OS X. В ней говорится: «asprintf() и vasprintf() динамически выделяют новую строку с помощью malloc(3)». - так что, на мой взгляд, это означает, что изменение размера буфера требует вызова realloc().
 – 
user529758
26 Мар 2013 в 22:58
Должно быть, Mac OS новее, чем моя, но это именно то слово, которое я искал. Неуклюжая версия. :: вздох ::
 – 
dmckee --- ex-moderator kitten
26 Мар 2013 в 23:00
1
«Из справочной страницы неясно, должно это или нет». - Это кристально ясно из справочной страницы ... вы просто очень плохо ее читаете. «Это неуклюжая версия» - она ​​была бы немного менее неуклюжей, если бы вы бессмысленно не инициализировали newstr значением NULL. А free - это функция - для ее вызова нужны круглые скобки.
 – 
Jim Balter
26 Мар 2013 в 23:11
2
Чтобы сделать существенное замечание: ret является аргументом out - *ret установлен, но его значение никогда не используется. Вы можете определить это из документации, потому что ... там сказано, что *ret установлен, и никогда не упоминается использование его значения. Таким образом, realloc об этом не может быть и речи.
 – 
Jim Balter
26 Мар 2013 в 23:16