Почему многие функции C используют указатели для передачи данных вместо использования "return"?

Это более или менее вопрос о методологии и обосновании. При программировании различных модулей ядра для Linux меня смешивает то, что я считаю неуклюжим способом проектирования функций. Например, чтобы получить индекс inode файла, заданный по его пути, мне пришлось использовать что-то вроде:

struct inode *inode;
struct path path;
kern_path(path_name, LOOKUP_FOLLOW, &path);
inode = path.dentry->d_inode;

Почему не просто функция, которая работает как:

struct inode inode;
struct path path = kern_path(path_name, LOOKUP_FOLLOW);
inode = path.dentry->d_inode;

Кажется гораздо более интуитивным.

Ответ 1

И что бы вы сделали с int, который kern_path возвращает?

Важно, чтобы функции могли возвращать какой-то код ошибки, чтобы пользователь мог обеспечить выполнение этой функции. Есть два очевидных варианта:

  • Верните код ошибки.

    Это означает, что вы должны принять другое значение, которое вы хотите вернуть в качестве параметра.

  • Возвращает значение.

    Это означает, что вы должны принять код ошибки в качестве параметра.

Так как вы можете только вернуть 1 значение в C, вам нужно взять что-то в качестве параметра, так как в конечном итоге вы хотите вернуть две вещи пользователю (код ошибки и значение).

Ответ 2

Ядро - это программа на языке C со специальными ограничениями. В частности, стек вызовов не может быть глубоким (IIRC, он ограничен 4 Кбайтами).

Когда вы возвращаете struct, ABI (см. x86-64 ABI...) обязывает (за исключением некоторых короткий struct фитинг в два слова) возвращенный struct проходит через стек. Таким образом, такой стиль будет способствовать созданию довольно больших кадров стека, следовательно, будет легче соответствовать пределу стека.

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

В x86-64/Linux, возвращающем структуру из двух скалярных значений (целые числа, указатели, перечисления, удвоения,....), определенно стоит того, см. this.

Ответ 3

Это позволяет сократить количество блоков памяти для копирования взад и вперед.

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

Ответ 4

Передача указателю на структуру, которая используется для возврата данных, полезна для передачи большого количества данных без фактического прохождения. Вы просто передаете указатель туда и обратно, а не гигантскую структуру.

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

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

Не говоря уже о том, что если вы хотите вернуть более 1 вещи, это почти единственный способ сделать это.

Хорошее преимущество - это очень четко определить интерфейс для драйвера. Объявите структуру и там ваш API. Ничего другого не происходит.

Ответ 5

Фактическая причина намного проще, чем другие ответы: это просто привычка, от ограничения, которое больше не существует. Все другие обоснования были изобретены после факта; обычно существует равное и противоположное оправдание противоположного.

В старых компиляторах C все аргументы и возвращаемое значение должны были помещаться в регистры. Это означало, что если бы они были больше, их нужно было бы отправить по адресу.