Функції, що повертають рядки, гарний стиль?


11

У своїх програмах на C мені часто потрібен спосіб скласти рядкове представлення моїх ADT. Навіть якщо мені не потрібно надрукувати рядок на екрані будь-яким способом, дуже охайно мати такий метод для налагодження. Тож ця функція часто з’являється.

char * mytype_to_string( const mytype_t *t );

Я фактично розумію, що у мене є (принаймні) три варіанти тут для обробки пам'яті для повернення рядка.

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

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

Альтернатива 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));і, отже, я не люблю повертати використану довжину, а скоріше вказівник на рядок. Динамічний коментар до бібліотеки дійсно важливий.
Øystein Schønning-Johansen

Чи не повинно це sizeof(buffer) - 1задовольняти \0термінатор?
Michael-O

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. Потрібно знайти цю посилання ....


0

Як додаток до відмінної відповіді @ ratchetfreak, я зазначу, що альтернатива №3 відповідає аналогічній парадигмі / схемі, що і стандартні бібліотечні функції С.

Наприклад, strncpy.

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

Дотримання цієї ж парадигми допоможе зменшити когнітивне навантаження для нових розробників (або навіть вашого майбутнього "), коли їм потрібно використовувати вашу функцію.

Єдина відмінність у вашій посаді полягала в тому, що destinationаргумент у бібліотеках С, як правило, вказується першим у списку аргументів. Тому:

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

-2

Крім того, що те, що ви пропонуєте зробити, - це поганий запах коду, альтернатива 3 мені найкраще звучить. Я також думаю, як @ gnasher729, що ви використовуєте неправильну мову.


Що саме ви вважаєте кодовим запахом? Будь ласка, докладно.
Халк

-3

Якщо чесно, ви можете перейти на іншу мову, де повернення рядка не є складною, трудомісткою та схильною до помилок операцією.

Ви можете розглянути C ++ або Objective-C, де ви можете залишити 99% свого коду без змін.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.