10
  • がない#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;
}
4

2 に答える 2

6

デフォルトでは、GCC は暗黙の宣言を可能にする C90 標準 (GNU 拡張 (参照) を使用) を使用します。あなたのケースの問題はisdigit、関数の暗黙的な宣言を作成するときにコンパイラを混乱させる可能性のある2つの異なる引数を使用した2つの呼び出しがあり、おそらくint isdigit(double)安全な側にあることを選択することです。もちろん、これは関数の間違ったプロトタイプです。つまり、実行時にライブラリ関数が呼び出されると、間違った引数で呼び出され、未定義の動作が発生します。

ヘッダー ファイルをインクルードする<ctype.h>と、正しいプロトタイプが作成され、コンパイラはそれが引数をisdigit取り、呼び出しのためにリテラルを整数にint変換できることを認識します。double48.448


リンクしている理由については、これらの関数マクロとして実装されている可能性がありますが、それは要件ではないためです。要件は、少なくとも C11 標準 (現時点では利用可能な古いバージョンはありません) では、これらの関数が現在のロケールを認識している必要があるため、マクロとしての実装がはるかに難しくなります。通常のライブラリ関数よりも簡単です。また、標準ライブラリは常にリンクされているため (GCC に特に指示しない限り)、関数を使用できます。

于 2015-11-17T12:41:42.567 に答える
4

まず第一に、#includeステートメントは とは何の関係もありませんlinking#前に inCがあるものはすべて、コンパイラやリンカーではなく、プリプロセッサを対象としていることを忘れないでください。

しかし、それは機能がリンクされなければならないということですよね?

別々の手順で手順を実行しましょう。

$ 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

ご覧のとおり、printfisdigitは未定義としてリストされています。コードはどこかから来なければなりませんね。

リンクを進めましょう...

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

ご覧のとおり、状況はわずかに改善されています。彼らがいたように、無力な孤独ではisdigitありません。両方の機能が によって提供されていることがわかります。しかし、それはどこですか?printfst.oGLIBC_2.2.5GLIBC

さて、最終的な実行可能ファイルをもう少し調べてみましょう...

$ 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)

あはは…ありますねlibc。したがって、指示はありませんが、リンカーはデフォルトで 3 つのライブラリとリンクしています。そのうちの 1 つは と の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}}

他の 2 つのライブラリは何ですか?

を掘り下げたときのことをよく覚えておいa.outてください。つまり、これらのシンボルに関連付けられたアドレスはありませんでした。printfisdigitUmemory

実際には、これが魔法の場所です。これらのライブラリは、古いシステムのようにリンク時ではなく、実行時に実際にロードされました。

それはどのように実装されていますか?まあ、遅延リンクのような専門用語が関連付けられています。それが行うことは、プロセスが関数を呼び出すときに、メモリアドレス(TEXTセクション)がない場合、Trap(制御が言語エンジンに渡されるときに、高級言語の専門用語で例外のようなもの)を生成することです。カーネルはそのようなものをインターセプトTrapし、それを動的ローダーに渡します。動的ローダーはライブラリをロードし、関連するメモリ アドレスを呼び出し元プロセスに返します。

物事を怠惰に行う方が事前に行うよりも優れているという理論的な理由は複数あります。これはまったく新しいトピックだと思いますが、それについては別の機会に説明します。

于 2015-11-17T13:33:01.503 に答える