Страницы

Поиск по вопросам

воскресенье, 15 марта 2020 г.

Объясните все возможные причины использования заголовков в мультифайловых проектах С

#c #gcc


Посмотрите в начало стр. 5 здесь и объясните популярно, зачем в принципе нужен заголовок
calc.h, если проект спокойно компилируется и запускается без заголовков вообще через: 
Andrey@AsusP5B ~/2
$ gcc driver.c calc.c -o prog

Andrey@AsusP5B ~/2
$ ./prog

Square of 5 is 25

Andrey@AsusP5B ~/2
$

Это что, новым компиляторам уже на заголовки наплевать или на прототипы функций,
или это чистота написания кода, или просто лучше заголовки лучше сразу создавать, чтобы
не было путаницы или х.з., так как в будущем они понадобятся для других целей... В
теории, ПОЧЕМУ?    


Ответы

Ответ 1



Все работает, потому что си компилятор полагается на то, что Вы самостоятельно угадали сигнатуру. Возьмите driver.c, закомментируйте #include "calc.h" и "сделайте ошибку в вызове функции, где-то так: printf("\nSquare of %d is %d\n", x, square()); Код скомпилируется и даже никто не ругнется. Но вот только работать он будет странно (как именно странно - зависит от ситуации, может упасть, а может выводить странные результаты). У меня, к примеру, выводит просто "Square of 5 is 1". Правильные заголовочные файлы позволяют компилятору знать правильную сигнатуру функции и находить подобные ошибки. Но почему же оно все-таки компилируется? Да все просто. Когда компилятор видит объявление функции, он генерирует для нее код и запоминает адрес начала в специальной таблице в виде имя-адрес (да, там нет параметров!). Когда нужно вставить вызов функции, просто смотрит в таблицу и поставляет "call адрес". Сами параметры перед этим напихает в стек push'ем. Но тут есть ещё одна хитрость. На самом деле вначале туда проставляются не адреса. Адреса уже проставляет линковщик. Именно по этой причине порядок компиляции файлов не столь важен. Почему же оно не работает, хотя и компилируется? Вызывающая сторона в стек параметры добавляет, но их фактическое кол-во нигде не отмечает. Вызываемая сторона просто достает с стека их. Но опять же, она не может никак узнать, что там именно то, что положила вызывающая сторона. Поэтому, что достали с стека, то и обработали. В с++ этот трюк уже не проходит. Там компилятор делает "манглироване имен". То есть в таблицу функций вставляет имя функции с хитрым описанием типов параметров. Это нужно, так как в С++ есть возможность создать несколько функций с одним именем и разным кол-вом/типом параметров.

Ответ 2



В данном случае все работает без calc.h потому, что функция square() из calc.c возвращает int, а это значение результата функции по умолчанию. Измените в calc.c на double square (int x) и Вы увидите, как все сломается. Update Смотрите, вот код и результаты avp@avp-xub11:hashcode$ more cf1.c cf2.c | cat :::::::::::::: cf1.c :::::::::::::: #include int main () { int x = 5; const char *fmt = "square of %d is " #ifdef TINT "%d\n" #else "%f\n" #endif ; printf(fmt, x, square(x)); return 0; } :::::::::::::: cf2.c :::::::::::::: #ifdef TINT int #else double #endif square (int x) { return x * x; } avp@avp-xub11:hashcode$ gcc cf1.c cf2.c -DTINT avp@avp-xub11:hashcode$ ./a.out square of 5 is 25 avp@avp-xub11:hashcode$ gcc cf1.c cf2.c avp@avp-xub11:hashcode$ ./a.out square of 5 is -0.000000 avp@avp-xub11:hashcode$ Update 2 Добавим calc.h #ifndef _CALC_H // это так называемый guard, почти всегда нужен в заголовочных файлах #define _CALC_H #ifndef TINT double #else int #endif square (int); #endif который в зависимости от TINT определяет либо int либо double square(). avp@avp-xub11:hashcode$ more cf1.c cf2.c | cat :::::::::::::: cf1.c :::::::::::::: #include #include "calc.h" int main () { int x = 5; const char *fmt = "square of %d is " #ifdef TINT "%d\n" #else "%f\n" #endif ; printf(fmt, x, square(x)); return 0; } :::::::::::::: cf2.c :::::::::::::: #include "calc.h" #ifdef TINT #define CAST int #else #define CAST (double) double #endif square (int x) { return CAST x * x; } avp@avp-xub11:hashcode$ gcc cf1.c cf2.c -DTINT avp@avp-xub11:hashcode$ ./a.out square of 5 is 25 avp@avp-xub11:hashcode$ gcc cf1.c cf2.c avp@avp-xub11:hashcode$ ./a.out square of 5 is 25.000000 avp@avp-xub11:hashcode$ И все, как видите, заработало. Конечно, в простых случаях (а на практике лучше все делать проще (это я тут от лени начал ваять шаблоны (templates))) никакой препроцессор (для условной компиляции, конечно же) не нужен.

Комментариев нет:

Отправить комментарий