2013-07-23 22 views
14

私はthis code from here(中国語)を読んでいます。 Cでグローバル変数をテストするコードが1つあります。変数aは、ファイルt.hに2回含まれています。ファイルfoo.cには、ある値のstruct bmainの機能が定義されています。 main.cファイルでは、初期化されていない2つの変数が定義されています。C異なるファイルで定義された同じグローバル変数

/* t.h */ 
#ifndef _H_ 
#define _H_ 
int a; 
#endif 

/* foo.c */ 
#include <stdio.h> 
#include "t.h" 

struct { 
    char a; 
    int b; 
} b = { 2, 4 }; 

int main(); 

void foo() 
{ 
    printf("foo:\t(&a)=0x%08x\n\t(&b)=0x%08x\n 
     \tsizeof(b)=%d\n\tb.a=%d\n\tb.b=%d\n\tmain:0x%08x\n", 
     &a, &b, sizeof b, b.a, b.b, main); 
} 

/* main.c */ 
#include <stdio.h> 
#include "t.h" 

int b; 
int c; 

int main() 
{ 
    foo(); 
    printf("main:\t(&a)=0x%08x\n\t(&b)=0x%08x\n 
     \t(&c)=0x%08x\n\tsize(b)=%d\n\tb=%d\n\tc=%d\n", 
     &a, &b, &c, sizeof b, b, c); 
    return 0; 
} 

UbuntuのGCC 4.4.3コンパイルを使用した後、結果は以下のようである:

foo: (&a)=0x0804a024 
    (&b)=0x0804a014 
    sizeof(b)=8 
    b.a=2 
    b.b=4 
    main:0x080483e4 
main: (&a)=0x0804a024 
    (&b)=0x0804a014 
    (&c)=0x0804a028 
    size(b)=4 
    b=2 
    c=0 

変数abは、二つの機能で同じアドレスを有するが、bのサイズが変更されました。私はそれがどのように機能するのか理解できません!

+3

あなたの質問がありますか? –

+2

ポインタを出力するために '%p'を使います。ヘッダに' foo'を追加する必要があります。 – Nobilis

+0

競合を解決するには、var staticを宣言します。 – snf

答えて

19

static struct { 
    char a; 
    int b; 
} b = { 2, 4 }; 

あなたがCの "一つの定義ルール" を違反していると、結果は未定義の動作です。「1つの定義ルール」は、そのような基準として形式的には規定されていない。私たちはさまざまなソースファイル(別名、翻訳単位)のオブジェクトを見ているので、 "外部定義"に関係しています。 「一つの外部定義」セマンティックは(C11 6.9 P5)綴られる:

外部定義はまた、(インライン定義以外の)関数またはオブジェクトの定義である外部宣言です。外部結合で宣言された識別子が式内で使用される場合(結果が整数定数であるsizeofオペレータまたは_Alignofオペレータのオペランドの一部以外の場合)、プログラム全体のどこかにが存在します;さもなければ、1つしかない。基本的に、あなただけのに許可されている意味

は一度せいぜいオブジェクトを定義します。 (otherwise節では、プログラムのどこでも使用されていない外部オブジェクトをまったく定義しないことができます)。bの2つの外部定義があります。 (C11 6.9.2 P1-2)、一つは、あなたがfoo.cで初期化した構造であり、他方はmain.c仮定義です:

オブジェクトの識別子の宣言がファイル有効範囲を持っている場合および初期化子である場合、 宣言は識別子の外部定義です。

初期化することなく、ストレージ・クラス指定せずに、またはストレージ・クラス指定staticとスコープファイル持つオブジェクトに対する識別子の宣言は、仮定義を構成しています。翻訳単位に識別子の仮定義が1つ以上含まれており、翻訳単位にその識別子の外部定義が含まれていない場合、その動作は翻訳単位にその識別子のファイルスコープ宣言が含まれているかのようになります。初期化子は0になります。

したがって、bという複数の定義があります。しかし、異なる種類のbを定義したという点で、別のエラーがあります。最初に、外部リンクを持つ同じオブジェクトに対する複数の宣言が許可されることに注意してください。ただし、2つの異なるソースファイルで同じ名前が使用されている場合、その名前は同じオブジェクト(C11 6.2.2 p2)を参照します。

プログラム全体を構成する翻訳単位とライブラリ 外部リンケージによる特定の識別子の宣言は、同じオブジェクトまたは 関数を示します。

Cは、同じオブジェクト(C11 6.2.7 P2)に宣言に厳しい制限を置く:

同じオブジェクトまたは関数を参照するすべての宣言は互換型を持たなければなりません。 この動作は未定義です。

各ソースファイルのbの型は実際には一致しないため、動作は未定義です。 (何が互換性のあるタイプはC11 6.2.7のすべてに詳細に記載されている構成するが、それは基本的に型が一致していることであることに帰着する。)

だからあなたはbのための2つの欠点があります。

  • 複数の定義。
  • 互換性のない複数の宣言があります。

技術的には、両方のソースファイルのint aの宣言も「1つの定義ルール」に違反します。

オブジェクトの識別子の宣言がスコープなしストレージ・クラス指定ファイルがある場合、その結合は、外部にある:a外部結合(C11 6.2.2 P5)を有することに留意されたいです。

しかしは、C11 6.9.2以前からの引用から、それらのint a仮定義は、外部定義され、そしてあなただけの上部にC11 6.9からの引用から、それらのいずれかの許可されています。

通常の免責事項は未定義の動作に適用されます。何かが起こる可能性があり、それはあなたが観察した行動を含むでしょう。


Cに共通の拡張が複数の外部の定義を可能にすることである、そして有益附属書J.5(C11 J.5.11)におけるC標準に記載されている:

複数存在してもよいですオブジェクトの識別子の1つの外部定義よりも、またはを明示的に使用しないで としてください。 定義が一致しない場合、または2つ以上が初期化された場合動作は定義されていません(6.9.2)。

(強調は私です。)aの定義が同意するので、そこに害はありませんが、bの定義は同意しません。この拡張は、コンパイラが複数の定義の存在について不平を言っていない理由を説明します。 C11 6.2.2の引用符から、リンカーは同じオブジェクトへの複数の参照を調整しようとします。

リンカーは通常、複数の翻訳単位で同じシンボルの複数の定義を調整するために2つのモデルのうちの1つを使用します。これらは「共通モデル」と「基準/基準モデル」です。 「共通モデル」では、同名の複数のオブジェクトがunionスタイルの方法で1つのオブジェクトに折りたたまれ、オブジェクトが最大の定義のサイズを占めるようになります。 「Ref/Def Model」では、それぞれの外部名には1つの定義しかない必要があります。

GNUツールチェーンは、デフォルトで「共通モデル」を使用し、単一の翻訳単位に対して厳密に1つの定義ルールを適用する「リラックスした参照/義務モデル」を使用しますが、複数の翻訳単位。

-fno-commonオプションを使用してGNUコンパイラで「共通モデル」を抑制することができます。

$ cat a.c 
#include <stdio.h> 
int a; 
struct { char a; int b; } b = { 2, 4 }; 
void foo() { printf("%zu\n", sizeof(b)); } 
$ cat b.c 
#include <stdio.h> 
extern void foo(); 
int a, b; 
int main() { printf("%zu\n", sizeof(b)); foo(); } 
$ gcc -fno-common a.c b.c 
/tmp/ccd4fSOL.o:(.bss+0x0): multiple definition of `a' 
/tmp/ccMoQ72v.o:(.bss+0x0): first defined here 
/tmp/ccd4fSOL.o:(.bss+0x4): multiple definition of `b' 
/tmp/ccMoQ72v.o:(.data+0x0): first defined here 
/usr/bin/ld: Warning: size of symbol `b' changed from 8 in /tmp/ccMoQ72v.o to 4 in /tmp/ccd4fSOL.o 
collect2: ld returned 1 exit status 
$ 

私は個人的にリンカによって発行された最後の警告を感じるが、常に解像度に関係なく提供されなければならない。私は私のシステムでこれをテストしたとき、それはあなたのようなコードのために、「厳格なREF /デフモデル」行動を起こしました複数のオブジェクト定義のモデルですが、ここにもそこにはありません。


参照:
Unfortunately, I can't give you the link to my copy of the C11 Standard
What are extern variables in C?
The "Beginner's Guide to Linkers"
SAS Documentation on External Variable Models

+1

通常、「def/ref model」と「common model」と呼ばれるものが2つの典型的な実装であることは注目に値する。後者は、典型的なUnix/Linuxリンカーが使用しているもので、観察された動作を引き起こします。フレーズ「共通モデル」は、1960年代の多くのメインフレームで実装されたFortran COMMONブロックを参照するように見えます。 – torek

+0

@torek:もう少しリサーチをしてくれてありがとう。私は答えを更新しました。 – jxh

+0

おそらく、翻訳単位は何ですか? – NickHalden

0

時間fooがコンパイルされる時に主がコンパイルされている場合はsizeof(INT)は4 、bはちょうどintとして再宣言された場合には、範囲内にあるb{2, 4}または8バイトの2つのintベクトルであります4のサイズは理にかなっています。また、次のスロット(int)が4バイト境界に位置合わせされるように、おそらく "a"の後にstructに "padding bytes"が追加されます。

-1

aとbはファイル内の同じポイントにあるため、同じアドレスを持ちます。 bが異なるサイズであるという事実は、変数がどこで始まるかは関係ありません。いずれかのファイルでaとbの間に変数cを追加した場合、bsのアドレスは異なります。

1

bは、リンカーがあなたのために解決することを決めたため、同じアドレスを持っています。

sizeofは、sizeofがコンパイル時間で評価されるため、異なる値を示します。この段階では、コンパイラはただ1つのb(現在のファイルで定義されているもの)を知っています。

+1

+1は、それが同じメモリ位置に 'b'を置くリンカであることを知っています。 – jxh

2

コードは、目的に応じて1つの定義ルールを破るようです。それは未定義の振る舞いを呼び出すでしょう。そうしないでください。

グローバル変数についてa:グローバル変数の定義は、複数の.cファイルにインクルードされ、複数の定義につながるため、ヘッダーファイルには定義しないでください。ヘッダーに宣言を入れ、定義を.cファイルの1つに入れてください。 t.hで

extern int a; 

foo.cのグローバル変数bについて

int a; 

では:ファイル内の変数を制限するstaticを使用し、それを複数回定義されていません。 foo.cので

:main.cの

static int b; 
+0

+1定義ルール違反を避けるための具体的な救済策。 – jxh

3

形式的には、複数回の外部リンクと同じ変数(または関数)を定義することは違法です。したがって、正式な観点から、プログラムの動作は未定義です。

実際には、外部リンケージを使用して同じ変数の複数の定義を可能にするのは、一般的なコンパイラの拡張機能(一般的な拡張機能、言語仕様で言及されています)です。しかし、適切に使用されるためには、それぞれの定義は同じ型で宣言しなければならない。そして、1つ以上の定義にイニシャライザを含めること。

あなたのケースは、共通の内線番号の説明と一致しません。あなたのコードはその共通拡張の副作用としてコンパイルされますが、その動作は未定義です。

+0

私はすでにこの回答を以前に投票していましたが、この回答は、最初に、複数の外部定義を可能にする共通の拡張を最初に取り上げました。 – jxh

関連する問題