С поддержкой macOS, Windows (MSVC) и Linux, как мне сделать следующее?
char *s;
func(&s, "foo");
if (<condition>) func(&s, "bar%s", "can")
/* want "foobarcan", and I don't know `strlen(s)` AoT */
Я пробовал с asprintf
(смог найти реализацию MSVC), но, похоже, это не сработало для такого рабочего процесса. fopencookie
и funopen
кажутся удобными, но недоступными в MSVC.
Может быть, есть какой-то чистый способ с realloc
создать NUL, заканчивающийся char*
в C?
Используйте vsnprintf
с n=0
, чтобы определить длину выходной строки. Тогда malloc
не менее length+1
байт памяти. Вызовите vsnprintf
еще раз с правильным значением n
, чтобы создать строку. Наконец, free
предыдущая строка, если она есть. Кстати, вам нужно инициализировать s
, например. char *s = NULL;
func(&s, "foo")
и func(&s, "bar%s", "can")
-- C не допускает перегрузки функций, как C++, поэтому, вероятно, вы имеете в виду func1(...)
и func2(...)
?
Дэвид: да. Я просто использовал эту настройку для соответствия asprintf
, это не обязательно должны быть эти типы (и можно инициализировать NULL
или 0). user3386109 хорошо, так что это самый чистый способ?
@AT Это самый чистый способ выяснить, сколько памяти вам нужно. Обратите внимание, что я бы использовал malloc
для выделения буфера, а не realloc
. Это позволяет вам делать такие вещи, как добавление существующей строки к себе, например. char *s = NULL; func(&s, "foo"); func(&s, "%s", s);
создаст строку «foofoo».
Как указано в комментариях, (v)snprintf
всегда возвращает количество байтов, которые были записаны в бы (исключая нулевой завершающий байт), даже если он усечен. Это приводит к тому, что предоставление функции с аргументом size
, равным 0
, возвращает длину форматируемой строки.
Используя это значение плюс длину нашей существующей строки (если применимо) плюс единицу, мы (повторно) выделяем соответствующий объем памяти.
Для конкатенации просто напечатайте отформатированную строку с правильным смещением.
Пример без проверки ошибок.
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *dstr(char **unto, const char *fmt, ...) {
va_list args;
size_t base_length = unto && *unto ? strlen(*unto) : 0;
va_start(args, fmt);
/* check length for failure */
int length = vsnprintf(NULL, 0, fmt, args);
va_end(args);
/* check result for failure */
char *result = realloc(unto ? *unto : NULL, base_length + length + 1);
va_start(args, fmt);
/* check for failure*/
vsprintf(result + base_length, fmt, args);
va_end(args);
if (unto)
*unto = result;
return result;
}
int main(void) {
char *s = dstr(NULL, "foo");
dstr(&s, "bar%s%d", "can", 7);
printf("[[%s]]\n", s);
free(s);
}
stdout
:
[[foobarcan7]]
Оговорка здесь в том, что вы не можете написать:
char *s;
dstr(&s, "foo");
s
должен быть инициализирован как NULL
, или функция должна использоваться непосредственно как инициализатор с первым аргументом, установленным на NULL
.
Этот и второй аргумент всегда обрабатываются как строка формата. Используйте другие способы предварительного выделения первой строки, если она содержит антисанитарные данные.
Пример эксплойта:
/* exploit */
char buf[128];
fgets(buf, sizeof buf, stdin);
char *str = dstr(NULL, buf);
puts(str);
free(str);
stdin
:
%d%s%s%s%s%d%p%dpapdpasd%d%.2f%p%d
Результат: Неопределенное поведение
Также стоит отметить, что s
не может быть NULL
, если строка, переданная в fmt
, содержит для нее спецификатор преобразования (например, "%s%s"
). Это становится немного глубже, так как вам нужен способ анализа fmt
, чтобы определить количество присутствующих спецификаторов преобразования, а затем сопоставить это с заданными аргументами, отличными от NULL. (угловой шкаф)
@DavidC.Rankin Извините, я не уверен, что полностью понимаю. Не могли бы вы уточнить «fmt
содержит спецификатор преобразования»? Я думаю, что пример вызова, который вызывает описанное поведение, поможет.
Конечно, скажем, str
— это NULL
, и вы хотите объединить его с "foo"
в func (&s, "%s%s", "foo")
, чтобы получить "foo"
. В этом случае ваш vsprintf
с fmt
из "%s%s"
должен включать как *s
, так и "foo"
, но *s
— это NULL
. Результирующая строка будет "(null)foo"
. Таким образом, вопрос, показывающий, что func(&s, "foo");
и func(&s, "bar%s", "can")
заканчиваются на "foobarcan"
, представляет собой проблему. Если вы ограничите один аргумент, как в первом случае, значением по умолчанию "%s"
, чтобы получить "foo"
, у вас все в порядке, но как объединить это с "%scan"
, чтобы получить "foobarcan"
- это рассол.
Насколько я понимаю вопрос, первый аргумент функции не соотносится ни с одной частью строки формата. Или, скорее, начальный %s
подразумевается, если применимо. Желаемая операция аналогична concat(s ? s : calloc(INFINITY, 1), format(fmt, ...args))
(где мы притворяемся, что format
имитирует sprintf
, но возвращает буфер, а s
всегда имеет достаточно места для результата; игнорируя освобождение памяти). "%s%s"
всегда должны следовать два varargs; УБ иначе. Хотя, возможно, я неправильно понял.
Одна проблема, которую я заметил, заключается в том, что dstr(&s, "hello %s", s)
, конечно, взорвется, так как s
будет аннулирован до написания. Это должно быть решаемо с помощью отдельного распределения вместо перераспределения.
Спасибо, в основном это работает, хотя иногда я получаю AddressSanitizer: attempting free on address which was not malloc()-ed
в строке realloc
, даже с этим простым тестом: char *s;dstr(&s, "foo%s", "bar");dstr(&s, "can%s", "haz");ASSERT_EQ(strcmp(s, "foofbarcanhaz"), 0);free(s);PASS();
О, это должно быть установлено на NULL
, понял, изменил это сейчас. +1
Вы не можете передать
char *s
иrealloc()
внутри функции, не возвращая и не перезаписывая исходный адрес, удерживаемый указателем. (либо передайте адресs
, например,char **s
, либо измените тип возвращаемого значения функции наchar *
и верните перераспределенноеs
. В противном случае вы должны знать длинуs
, чтобы не выходить за пределыs
. Вы не можете вызыватьrealloc()
наs
для начального распределения, если толькоs
не инициализированNULL
.