Правильный путь возвращает строку из функции
У меня есть функция, которая задает два целых числа и возвращает строку. Прямо сейчас у меня есть это:
char* myfunc( int a, int b, int* len )
{
int retLen = ...
char* ret = malloc( retLen + 1 );
if ( len != NULL )
{
*len = retLen;
}
return ret;
}
Однако большинство функций в библиотеке C имеют тенденцию делать что-то большее:
int myfunc( char* ret, int a, int b )
{
...
return retLen;
}
Затем вы должны выделить память для заполнения функции. Это позволяет вам немного походить на выбор, где выделена строка.
В этом случае, хотя в функции требуется определенная математика, чтобы получить длину, и нет причин иметь буфер любого размера, кроме необходимого. Верхний предел размера буфера не существует (хотя это не разумно).
Что считается хорошей практикой при возвращении строки, длина которой динамически найдена с учетом входных данных?
Ответы
Ответ 1
Образец, который я видел в программах в режиме ядра:
- Вы вызываете функцию один раз, с некоторой выделенной памятью, если у вас есть какие-то доступные, или null как параметр, если у вас не было никого.
- Если у вас выделена память и функция ее достаточно, она помещает результат в эту память и возвращает OK
- Если у вас не было памяти, чтобы пройти, или память прошла слишком мало, функция возвращает ERROR_NOT_ENOUGH_MEMORY и помещает в выходной параметр необходимую память.
- Затем вы выделяете эту необходимую память и снова вызываете функцию
Пример:
int myfunc(
__out char* output,
__in size_t given,
__out size_t needed_or_resulted,
extra params ...
){
... implementation
}
needed_or_resulted
также может использоваться для передачи того, какая часть данной памяти была использована в случае успеха.
Используется как:
int result = myfunc(output, given, needed_or_resulted, extra params ...);
if(result == OK) {
// all ok, do what you need done with result of size "needed_or_resulted" on "output"
} else if(result == ERROR_NOT_ENOUGH_MEMORY) {
output = malloc(needed ...
result = myfunc(output, given, needed_or_resulted, extra params ...);
if(result == OK) {
// all ok, do what you need done with result of size "needed_or_resulted" on "output"
} else if(result == ERROR_OTHER) {
// handle other possible errors
} else {
// handle unknown error
}
} else if(result == ERROR_OTHER) {
// handle other possible errors
} else {
// handle unknown error
}
Ответ 2
Последнее лучше, потому что он подсказывает в вызывающем абоненте о том, кто несет ответственность за освобождение памяти. Первый вызывает большие проблемы, если вызывающий и вызываемый используют разные реализации malloc (например, в Windows, отладка и выпуск часто используют несовместимые модели памяти).
Ответ 3
Вы правы в том, почему предпочтительна подпись int myfunc( char* ret, int a, int b )
. На самом деле это объясняет другое: почему длина должна быть возвращена (размер буфера в MAX
, поэтому нам обычно нужно сообщить вызывающему, сколько из того, что мы действительно использовали).
Когда вы выделяете строки внутри функции, вы обычно не возвращаете размер строки, потому что strlen
можно использовать, чтобы найти это. Посмотрите на strdup
для примера функции, которая динамически выделяет строки. Поэтому я бы изменил подпись вашей функции на
char* myfunc( int a, int b) {
...
}
Ответ 4
Следуйте интерфейсу snprintf
, стандартной функции с точно такой же проблемой:
size_t myfunc(char *s, size_t n, int a, int b);
выходные байты, выходящие за пределы n-1, должны быть отброшены, а не записывается в массив, а нулевой байт записывается в конце байты, фактически записанные в массив.
После успешного завершения функция snprintf() должна вернуть количество байтов который был бы записан в s, был бы достаточно большим, исключая завершающий нуль > байт.
Если значение n равно нулю при вызове snprintf(), ничего не должно быть написано, количество байтов, которое было бы написано, было n достаточно большой, исключая завершающий нуль, возвращается, и s может быть нулевым указателем.
Типичное использование:
size_t needed = myfunc(0, 0, a, b) + 1;
char *buf = malloc(needed);
if (buf) {
myfunc(buf, needed, a, b);
}
Вы можете включить nul-байт в возвращенный счет - он делает код вызова более простым, хотя и немного менее знакомым людям, используемым для стандартного snprintf
.
Если вычисление retLen
является удивительно дорогостоящим, может существовать аргумент для функции, которая вычисляет ее, когда она генерирует строку, и возвращает выделенный буфер нужного размера (возможно, с realloc
ed вдоль пути). Но я обычно не думал об этом. Для удобства пользователей, которые хотят выделить, просто поместите вышеуказанный код в функцию myfunc_alloc
и неважно, что он дублирует немного работы. Пользователи, у которых уже есть буфер, могут вызвать myfunc
прямо так:
if (myfunc(buf, bufsize, a, b) >= bufsize) {
printf("buffer too small, string (%s) has been truncated\n", buf);
}
Ответ 5
Я передал бы char *
по ссылке. Если функция выполняется успешно, выделите строку и присвойте ей ссылку на указатель и верните длину строки. Если ошибка встречается, установите errno и return -1.
int myfunc( int a, int b, char ** str)
{
int retLen;
/* code to calculate string length required */
if (!(str))
{
errno = EINVAL;
return(-1);
};
if (!(*str = malloc(retLen)))
return(-1);
/* calculate new value and store to string */
return(retLen);
}