2015-08-18 5 views
1

Я обнаружил, что использую параметры функции как для ввода, так и для вывода, и мне было интересно, будет ли то, что я делаю, укусить меня позже.Использование функциональных параметров как для входа, так и для вывода

В этом примере buffer_len является таким параметром. Он используется foo, чтобы определить размер буфера и сообщить вызывающему абоненту main, сколько из буфера было израсходовано.

#define MAX_BUFFER_LENGTH 16 
char buffer[MAX_BUFFER_LENGTH] = {0}; 

void main(void) 
{ 
    uint32_t buffer_len = MAX_BUFFER_LENGTH; 

    printf("BEFORE: Max buffer length = %u", buffer_len); 

    foo(buffer, &buffer_len); 

    printf("BEFORE: Buffer length used = %u", buffer_len); 
} 

void foo(char *buffer, uint32_t *buffer_len) 
{ 
    /* Remember max buffer length */ 
    uint32_t buffer_len_max = *buffer_len; 
    uint32_t buffer_len_left = buffer_len_max; 

    /* Add things to the buffer, decreasing the buffer_len_left 
     in the process */ 
    ... 

    /* Return the length of the buffer used up to the caller */ 
    *buffer_len = buffer_len_max - buffer_len_left; 
} 

Это нормально делать?

EDIT:

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

typedef struct 
{ 
    char *data_ptr; 
    uint32_t length_used; 
    uint32_t length_max; 
} buffer_t; 

#define ACTUAL_BUFFER_LENGTH 16 
char actual_buffer[ACTUAL_BUFFER_LENGTH] = {0}; 

void main(void) 
{ 
    buffer_t my_buffer = { .data_ptr = &actual_buffer[0], 
          .length_used = 0, 
          .length_max = ACTUAL_BUFFER_LENGTH }; 
} 
+1

Это «ОК», если оно задокументировано, но на самом деле не является предпочтительным способом работы. Почему бы не вернуть функцию длины и оставить параметр длины буфера как регулярный 'uint32_t' (или, возможно,' size_t', поэтому вы можете передать 'sizeof (buffer)' функции)? –

+2

Это синтаксически нормально, но лично я предпочитаю коды возврата. Если это невозможно, я бы хотел, чтобы * выделенный * выходной параметр, * особенно *, если он является передачей по ссылке. (Я мог бы не обращать внимания и продолжать в предположении, что мой «buffer_len» по-прежнему сохраняет исходное значение.) – DevSolar

+1

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

ответ

3

Для оригинальной версии этого вопроса, где вызываемая функция не возвращает значения, вы получили три одинаковых ответов, все грубо говоря «Да, но ...»:

Jonathan Lefflersaid:

Это «ОК», если оно задокументировано, но на самом деле не является предпочтительным способом работы. Почему бы не вернуть функцию длины и оставить параметр длины буфера как обычный uint32_t (или, возможно, size_t, так что вы можете передать sizeof(buffer) функции)?

DevSolarsaid:

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

BeyelerStudiossaid:

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

Единоборство примечательно.

Вопрос был обновлен, чтобы указать, что вместо возврата void возвращаемое значение функции будет использоваться для другой цели. Это полностью меняет оценку.

Не показывать функцию void, если ваша настоящая функция вернет значение. Это полностью изменяет ответы. Если вам нужно вернуть более одного значения, то параметр входа в состояние ОК (даже необходимо - getchar() - примерный пример), хотя параметр pure in и отдельный параметр pure out могут быть лучше. Использование структуры тоже ОК.

Возможно, я должен немного объяснить «встречный пример». Функция getchar() возвращает значение, указывающее на отказ, или значение char. Это приводит к многим ловушкам для начинающих (потому что getchar() возвращает int, а не char, как следует из названия). Было бы лучше, в некотором смысле, если функция была:

bool get_char(char *c); 

возвращение true, если он читает символ и false, если это не удается, и присвоение значения символа в c. Его можно использовать как:

char c; 
while (get_char(&c)) 
    …use character just read… 

Это случай, когда функции необходимо вернуть два значения.

Возвращаясь к предлагаемому пересмотренному коду в вопросе, код с использованием структуры.

Это совсем не плохая идея; часто разумно упаковывать набор значений в структуру. Было бы очень полезно, если вызываемая функция должна вычислить некоторое значение, которое оно вернет, и тем не менее оно также изменяет массив буфера и должно сообщать о количестве записей в нем (а также знать, сколько места для использоваться). Здесь сохранение «свободного пространства» отдельно от «используемого пространства» является, безусловно, предпочтительным; будет легче увидеть, что происходит, чем иметь параметр «in-out», который информирует функцию о том, сколько места доступно при входе и сообщает, сколько места было использовано при выходе. Даже если бы он сообщил, сколько места по-прежнему доступно на выходе, было бы труднее использовать.

Возврат к исходному диагнозу: да, параметр in-out является технически законным, и его можно заставить работать, но он не так прост в использовании, как отдельные значения.


Side Примечание: void main(void) не стандартный способ написания main() - см What should main() return in C and C++? для полного рассказа.

1

Нет ничего плохого в использовании того же буфера для ввода и вывода, но он, вероятно, ограничивает использование функции в другом месте. Например, что, если вы хотите использовать его с двумя разными значениями? (по какой-то причине в качестве пользователя функции мне нужно, чтобы оригинал сохранялся). В примере, который вы указали, нет никакого вреда при принятии двух параметров в функции, а затем просто передайте один и тот же указатель в два раза. Затем вы завернули оба использования и, вероятно, упростили код функции.

Для более сложных типов данных, таких как массивы, а также той же проблемы выше, вам нужно убедиться, что ваша функция не нуждается в большем выходе, или если она сжимает буфер, который вы memset (0 ..) разница и так далее.

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