Функции, возвращающие строки, хороший стиль?

11

В моих программах на C мне часто нужен способ сделать строковое представление моих ADT. Даже если мне не нужно выводить строку на экран каким-либо образом, очень неплохо иметь такой метод для отладки. Так что такая функция часто появляется.

char * mytype_to_string( const mytype_t *t );

На самом деле я понимаю, что у меня есть (по крайней мере) три варианта обработки памяти для возвращаемой строки.

Альтернатива 1: сохранение возвращаемой строки в массиве статических символов в функции. Мне не нужно много думать, за исключением того, что строка перезаписывается при каждом вызове. Что может быть проблемой в некоторых случаях.

Альтернатива 2: выделите строку в куче с помощью malloc внутри функции. Действительно аккуратный, так как мне не нужно думать о размере буфера или перезаписи. Тем не менее, я должен помнить free () строку после завершения, а затем мне также нужно назначить временную переменную, чтобы я мог освободить. и тогда выделение кучи действительно намного медленнее, чем выделение стека, поэтому будьте узким местом, если это повторяется в цикле.

Альтернатива 3: передать указатель на буфер и позволить вызывающей стороне выделить этот буфер. Подобно:

char * mytype_to_string( const mytype_t *mt, char *buf, size_t buflen ); 

Это приносит больше усилий звонящему. Я также заметил, что эта альтернатива дает мне другой вариант порядка аргументов. Какой аргумент я должен иметь первым и последним? (на самом деле шесть возможностей)

Итак, что я должен предпочесть? Есть почему? Есть ли какой-то неписаный стандарт среди разработчиков C?

Ойстейн Шеннинг-Йохансен
источник
3
Просто замечание по наблюдению, большинство операционных систем используют опцию 3 - вызывающая сторона в любом случае выделяет буфер; сообщает указатель буфера и емкость; callee заполняет буфер, а также возвращает фактическую длину строки, если буфера недостаточно. Пример: sysctlbynameв OS X и iOS
rwong

Ответы:

11

Методы, которые я видел больше всего, это 2 и 3.

Пользовательский буфер довольно прост в использовании:

char[128] buffer;
mytype_to_string(mt, buffer, 128);

Хотя большинство реализаций будет возвращать количество используемого буфера.

Вариант 2 будет медленнее и опасен при использовании динамически связанных библиотек, где они могут использовать разные среды выполнения (и разные кучи). Таким образом, вы не можете освободить то, что было размещено в другой библиотеке. Это тогда требует free_string(char*)функции, чтобы иметь дело с этим.

чокнутый урод
источник
Спасибо! Я думаю, что мне больше всего нравится Альтернатива 3. Однако я хочу иметь возможность делать что-то вроде: printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));и, следовательно, я не буду возвращать используемую длину, а указатель на строку. Динамический комментарий библиотеки действительно важен.
Ойстейн Шеннинг-Йохансен,
Разве это не sizeof(buffer) - 1должно удовлетворять \0терминатора?
Майкл-О
1
@ Michael-O нет, в размер буфера включен нулевой термин, означающий, что максимальная строка, которую можно вставить, на 1 меньше переданного размера. Это шаблон, который безопасная строка работает в стандартной библиотеке, например, как snprintf.
фрик с трещоткой
@ratchetfreak Спасибо за разъяснения. Было бы неплохо расширить ответ с этой мудростью.
Майкл-О
0

Дополнительная идея дизайна для # 3

По возможности также mytypeукажите максимальный размер, необходимый для того же файла .h, что и mytype_to_string().

#define MYTYPE_TO_STRING_SIZE 256

Теперь пользователь может кодировать соответственно.

char buf[MYTYPE_TO_STRING_SIZE];
puts(mytype_to_string(mt, buf, sizeof buf));

порядок

Размер массивов, когда он первый, позволяет использовать типы VLA.

char * mytype_to_string( const mytype_t *mt, size_t bufsize, char *buf[bufsize]); 

Не так важно с одним измерением, но полезно с 2 или более.

void matrix(size_t row, size_t col, double matrix[row][col]);

Я вспоминаю, что чтение следующего размера является предпочтительной идиомой в следующем C. Нужно найти эту ссылку ....

Chux - Восстановить Монику
источник
0

В дополнение к отличному ответу @ ratchetfreak, я хотел бы отметить, что альтернатива № 3 следует той же парадигме / шаблону, что и стандартные функции библиотеки C.

Например, strncpy.

char * strncpy ( char * destination, const char * source, size_t num );

Следование той же парадигме поможет снизить когнитивную нагрузку для новых разработчиков (или даже для вашего будущего Я), когда им нужно использовать вашу функцию.

Единственная разница с тем, что у вас есть в посте, состоит в том, что destinationаргумент в библиотеках C, как правило, указывается первым в списке аргументов. Так:

char * mytype_to_string( char *buf, const mytype_t *mt, size_t buflen ); 
Джон Го-Соко
источник
-2

Помимо того, что то, что вы предлагаете сделать, является неприятным запахом кода, альтернатива 3 звучит лучше для меня. Я также думаю, как @ gnasher729, что вы используете не тот язык.

Джон Пферсич
источник
Что именно вы считаете запах кода? Пожалуйста, дополните.
Халк
-3

Если честно, вы можете переключиться на другой язык, где возврат строки не является сложной, трудоемкой и подверженной ошибкам операцией.

Вы можете рассмотреть C ++ или Objective-C, где вы можете оставить 99% своего кода без изменений.

gnasher729
источник