#c #unix #open-source
#c #unix #с открытым исходным кодом
Вопрос:
Я предположил, что одна из наиболее используемых системных функций ( ls
) одной из самых известных операционных систем (linux), написанная одним из самых авторитетных программистов (Ричард Столлман), может быть примером действительно хорошо написанного кода.
Итак, поскольку это открытый исходный код, я решил взглянуть на код (см., например, здесь). Там я нашел несколько функций, определенных после main()
, следовательно, после их вызова, что, как я ожидал, будет довольно необычно.
Прокомментирует ли это какой-нибудь опытный программист на C?
Комментарии:
1. Почему вы ожидали, что это будет необычно?
2. Ваши предположения неверны, и ваше удивление необоснованно.
3. Так получилось, что мне понравились все ответы, что вызвало у меня проблему с выбором принятого. Я выбрал первое с помощью Gui13, возможно, более тщательное, но я также обнаружил, что с помощью fvu это довольно полезно. Спасибо вам всем.
Ответ №1:
В том, что здесь сделал Столлман, нет абсолютно ничего плохого.
Язык C допускает прямое объявление функции, которая будет определена впоследствии.
Это имеет много преимуществ и должно рассматриваться не как плохое поведение, а скорее как очень хорошее поведение.
Преимущества (не исчерпывающие):
— дают программисту представление об API, предоставляемом кодом C, в кратком обзоре, без необходимости просматривать весь код
— разрешает использование заголовочных файлов, в которых вы объявляете функцию, которая будет определена позже в процессе компиляции. Чтобы вам не приходилось определять свою функцию каждый раз, когда вы ее используете..
В случае этой ls
реализации он просто предварительно объявил функции, которые он будет использовать в main()
, но если вы посмотрите внимательно, функция main появляется первой. Это, скорее всего, для удобства чтения, чтобы вам не приходилось прокручивать весь путь вниз, чтобы добраться до точки входа в программу.
Обратите внимание, что здесь важен словарь:
— объявление функции означает: просто сообщает компилятору, что где-то в вашем коде будет определена функция с таким же именем.
— определение функции: фактическая реализация функции
int my_function( char *text); // function declaration, no implementation
int main( int argc, char **argv)
{
return my_function(argv[0]); // use of the declared function
}
// actual function definition / implementation
int my_function( char *text )
{
printf("%sn", text);
}
Редактировать: более внимательно изучив код, вы можете увидеть, что Столлман не переадресовывал все свои функции. У него также довольно странная манера определения функций. Я приписываю это устареванию кода, который датируется 1985 годом, когда компилятор C не был так хорошо определен, как сегодня.
Должно быть, этот тип использования функций был разрешен до их объявления или определения.
И последнее, но не менее важное: последнюю версию ls
исходного кода можно найти здесь: http://coreutils.sourcearchive.com/documentation/7.4/ls_8c-source.html ,
с гораздо более совместимым с C99 кодированием, чем версия ’85 (назад в будущее).
Комментарии:
1. в настоящее время вы все еще можете использовать необъявленные функции, компилятор просто ПРЕДУПРЕДИТ вас, что он использовал неявное объявление. Тонны удовольствия, если во многих случаях пренебрегать этим предупреждением.
2. Правильно, обратите внимание, что компилятор фактически рассмотрит эту функцию как возвращающую
int
, поэтому, если вы определите ее позже как возвращающую что-то другое, вы получите реальную ошибку 🙂3. Основная проблема заключается в том, что в коде не указаны ни возвращаемый тип, ни параметры для прототипов. Это очень опасный и очень плохой стиль. Если бы определения функций были идентичны объявлениям функций, тогда это было бы приемлемо.
4. «И последнее, но не менее важное, последняя версия исходного кода ls» Вау… Я всегда задавался вопросом, почему я никогда не пробовал Linux, и теперь я знаю почему. Я позабочусь о том, чтобы сохранить дополнительную безопасную дистанцию до следующей Linux-машины, с которой я столкнусь.
5. @Lundin: Неудивительно, что их «Hello World» приближается к 600 КБ в tar.gz : ftp.gnu.org/gnu/hello/hello-2.7.tar.gz 🙂
Ответ №2:
В C вы обычно определяете прототипы функций в заголовочных файлах, затем включаете заголовочные файлы, чтобы функции можно было безопасно определять после их вызова.
В приведенном вами файле примера программа достаточно мала, чтобы прототипы просто помещались в начало файла, перед объявлениями процедур, но применяется тот же принцип.
РЕДАКТИРОВАТЬ: Кроме того, этот файл создан до K amp; R C, что довольно круто, но есть некоторые существенные отличия от современного C, вам не обязательно имитировать его.
Ответ №3:
Концептуально код будет читаться как хорошая спецификация
- Сначала определите интерфейс, который использует основная точка входа
- Затем определите основную точку входа
- Тогда поведение программы в идеальном случае концептуально полностью определено.
- Наконец, реализуйте интерфейс (определения после
main
).
То, что он опускает объявления функций, которые должны быть помещены перед main
(чтобы удовлетворить первому маркеру, определяющему интерфейс), считается плохим стилем многими программистами в отношении современного C.
Однако в коде используются определения функций в старом стиле, для которых в их объявлениях не определяются типы параметров вызывающей стороне. Предположительно, обновление этого кода до современного C сломало бы множество очень старых систем, которые все еще используют компиляторы до ANSI.
Комментарии:
1. @Philip Я изначально действительно говорил об общем стиле объявления функций перед main и определения их впоследствии. Мне не очень нравится код этой программы на ls, в частности.
Ответ №4:
Для меня это пережиток эпохи, предшествовавшей созданию прототипа, взгляните на более новый код, и довольно часто вы увидите перевернутый порядок кода. Основным недостатком для меня является то, что вам приходится объявлять функцию сверху, а затем саму функцию намного ниже. Если вы измените сигнатуру функции, вам также придется изменить в двух местах. Пожалуйста, обратите внимание, что это также относится к статическим функциям, функция, которая вообще не использовалась в этом фрагменте кода… Конечно, эта штука будет компилироваться, если вы этого не сделаете, но вы будете укушены неявными объявлениями C раньше или позже.
Серьезно, найдите себе какой-нибудь более свежий код, чем какой-нибудь олдскульный C в стиле K amp; R. с тех пор язык C значительно эволюционировал, вы можете приобрести пару вредных привычек в процессе.
Комментарии:
1. Согласен, этот код ужасен и настолько далек от уровня техники, насколько это возможно. Сделайте себе одолжение и забудьте, что вы когда-либо видели это.
Ответ №5:
Перед вызовом функции у вас должен быть прототип в области видимости. Определение функции служит прототипом
/* this is a definition and a prototype */
int fx1(void) {
return 42;
}
/* this is only a prototype */
int fx2(int, const char*);
int main(void) {
fx1(); /* ok, prototype in scope */
fx2(42, "foobar"); /* ok, prototype in scope */
}
/* fx2 definition.
** it is undefined behaviour if the prototype here
** does not match the previous prototype */
int fx2(int k, const char *t) {
return strlen(t) - k;
}
Часто прототипы объявляются в заголовочных файлах, которые включаются в начало файлов реализации, перед любым кодом.
#include "prototypes.h"
/* define functions in any order: prototypes are all in scope */