Подтвердить что ты не робот

Почему я могу связать без включения ctype.h

  • Без #include<ctype.h> следующая программа выдает 1 и 0.
  • При включении он выводит 1 и 1.

Я использую TDM-GCC 4.9.2 64-бит. Интересно, что реализация isdigit находится в первом случае, а почему она способна связать.

#include<stdio.h>
//#include<ctype.h>
int main()
{
    printf("%d %d\n",isdigit(48),isdigit(48.4));
    return 0;
}
4b9b3361

Ответ 1

По умолчанию GCC использует стандарт C90 (с расширениями GNU (ссылка)), которая допускает неявные объявления. Проблема в вашем случае состоит в том, что у вас есть два вызова isdigit с двумя разными аргументами, которые могут смутить компилятор, когда он создает неявное объявление функции, и он, вероятно, выбирает int isdigit(double) для безопасности. Это, конечно, неправильный прототип функции, а это значит, что когда функция библиотеки вызывается во время выполнения, она вызывается с неправильными аргументами, и вы будете иметь поведение undefined.

Когда вы включаете заголовочный файл <ctype.h>, есть правильный прототип, а затем компилятор знает, что isdigit принимает int и может преобразовать double literal 48.4 в целое число 48 для вызова.


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

Ответ 2

Прежде всего #include утверждения не имеют ничего общего с linking. Помните, что что-либо с # in-front в C предназначено для препроцессора, а не для компилятора или компоновщика.

Но при этом функция должна быть связана, не так ли?

Сделайте шаги в отдельных шагах.

$ gcc -c -Werror --std=c99 st.c 
st.c: In function ‘main’:
st.c:5:22: error: implicit declaration of function ‘isdigit’ [-Werror=implicit-function-declaration]
     printf("%d %d\n",isdigit(48),isdigit(48.4));
                      ^
cc1: all warnings being treated as errors

Ну, как вы видите, gcc lint (статический анализатор) работает!

Что бы мы не проигнорировали его...

$ gcc -c  --std=c99 st.c 
st.c: In function ‘main’:
st.c:5:22: warning: implicit declaration of function ‘isdigit’ [-Wimplicit-function-declaration]
     printf("%d %d\n",isdigit(48),isdigit(48.4));

На этот раз только предупреждение. Теперь у нас есть объектный файл в текущем каталоге. Пусть проверит его...

$ nm st.o 
                 U isdigit
0000000000000000 T main
                 U printf

Как вы можете видеть, как printf, так и isdigit отображается как undefined. Так что код должен откуда-то исходить откуда-то?

перейдите к ссылке...

$ gcc st.o
$ nm a.out | grep  'printf\|isdigit'
                 U [email protected]@GLIBC_2.2.5
                 U [email protected]@GLIBC_2.2.5

Ну, как вы видите, ситуация мягко улучшена. Поскольку isdigit и printf не являются беспомощными одиночками, как в st.o. Вы можете видеть, что обе функции предоставлены GLIBC_2.2.5. Но где это GLIBC?

Хорошо рассмотрим окончательный исполняемый файл еще немного...

$ ldd a.out 
        linux-vdso.so.1 =>  (0x00007ffe58d70000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb66f299000)
        /lib64/ld-linux-x86-64.so.2 (0x000055b26631d000)

AHA... существует libc. Итак, получается, что вы не указали никаких инструкций, компоновщик связывается с 3-мя библиотеками по умолчанию, один из них - это libc, который содержит как printf, так и isdigit.

Вы можете увидеть поведение компоновщика по умолчанию:

$gcc -dumpspec
*link:
%{!r:--build-id} %{!static:--eh-frame-hdr} %{!mandroid|tno-android-ld:%{m16|m32|mx32:;:-m elf_x86_64}                    %{m16|m32:-m elf_i386}                    %{mx32:-m elf32_x86_64}   --hash-style=gnu   --as-needed   %{shared:-shared}   %{!shared:     %{!static:       %{rdynamic:-export-dynamic}       %{m16|m32:-dynamic-linker %{muclibc:/lib/ld-uClibc.so.0;:%{mbionic:/system/bin/linker;:/lib/ld-linux.so.2}}}       %{m16|m32|mx32:;:-dynamic-linker %{muclibc:/lib/ld64-uClibc.so.0;:%{mbionic:/system/bin/linker64;:/lib64/ld-linux-x86-64.so.2}}}       %{mx32:-dynamic-linker %{muclibc:/lib/ldx32-uClibc.so.0;:%{mbionic:/system/bin/linkerx32;:/libx32/ld-linux-x32.so.2}}}}     %{static:-static}};:%{m16|m32|mx32:;:-m elf_x86_64}                    %{m16|m32:-m elf_i386}                    %{mx32:-m elf32_x86_64}   --hash-style=gnu   --as-needed   %{shared:-shared}   %{!shared:     %{!static:       %{rdynamic:-export-dynamic}       %{m16|m32:-dynamic-linker %{muclibc:/lib/ld-uClibc.so.0;:%{mbionic:/system/bin/linker;:/lib/ld-linux.so.2}}}       %{m16|m32|mx32:;:-dynamic-linker %{muclibc:/lib/ld64-uClibc.so.0;:%{mbionic:/system/bin/linker64;:/lib64/ld-linux-x86-64.so.2}}}       %{mx32:-dynamic-linker %{muclibc:/lib/ldx32-uClibc.so.0;:%{mbionic:/system/bin/linkerx32;:/libx32/ld-linux-x32.so.2}}}}     %{static:-static}} %{shared: -Bsymbolic}}

Каковы две другие библиотеки?

Хорошо помните, когда вы врывались в a.out, оба printf и isdigit по-прежнему отображались как U, что означает неизвестность. Другими словами, не было адреса memory, связанного с этими символами.

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

Как это реализовано? Ну, это связано с жаргоном, что-то вроде ленивой ссылки. Что происходит, когда процесс вызывает функцию, если нет адреса памяти (раздел ТЕКСТ), он генерирует Trap (что-то вроде исключения на языке жаргонов высокого уровня, когда управление передается движку языка), Ядро перехватывает такой Trap и передает его динамическому загрузчику, который загружает библиотеку и возвращает связанный адрес памяти в процесс вызывающего абонента.

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