externについて

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
akasann

externについて

#1

投稿記事 by akasann » 2年前

Dxライブラリの本をゆっくりと読みながら4章終わりまで来ました。やってて、ゲームプログラミングの流れというものは、なんとなくですがわかりました。いくつか疑問になった点があるので、質問したいと思います。

1.件名の通りextern宣言の使い方がいまいち理解できません。ネットで調べてみると、モジュールが・・・関係?していることが分かったんですが・・・色々教えていただけるとありがたいです。モジュールは実行ファイルという形で理解しているのですが、あっているのでしょうか?

2.疑問に思ったこととして、cppファイルとヘッダーファイルを二つ用意しているところが不思議に思いました。例えば、画像を読み込むところとしてloading.cppとloading.hを用意しています。loading.hだけではダメなのでしょうか?質問の意味が理解できるか分りませんがお応えしてくれるとうれしいです。

おまけとして、AOJの問題にも取り組んでいるのですが、前のDight Numberについては解決しました。次の問題として最小公倍数の問題に取り組んでいます。次のコードに 50000000 と 30000000 を標準入力すると -14 という値が出てきます。何か別のやり方があるのでしょうか?・・・

コード:

#include<stdio.h>

int GCD(int m,int n){
	if(n==0)
		return m;
	else
		return GCD(n,m%n);
}

int LCM(int m,int n){
	return m*n/GCD(m,n);
}

int main(void){
	int a,b;
	while(scanf("%d %d",&a,&b)!=EOF){
		printf("%d %d\n",GCD(a,b),LCM(a,b));
	}
	return 0;
}

box
記事: 1735
登録日時: 8年前

Re: externについて

#2

投稿記事 by box » 2年前

akasann さんが書きました: おまけとして、AOJの問題にも取り組んでいるのですが、前のDight Numberについては解決しました。次の問題として最小公倍数の問題に取り組んでいます。次のコードに 50000000 と 30000000 を標準入力すると -14 という値が出てきます。何か別のやり方があるのでしょうか?・・・
50000000 と 30000000 をかけ算している時点で、int型で扱える数値の範囲を超えているような気がする。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

hide

Re: externについて

#3

投稿記事 by hide » 2年前

モジュールについて
定義された用語としてちゃんとあるのかは調べていませんが、
おそらくこの場合は機能ごとに分割されたプログラムの一部、でいいでしょう。
ゲームプログラムだったら、プレイヤーモジュールとかマップモジュールとかでしょうね。
これらをそれぞれ別のファイルに書くことによって下記のスコープ等を分けてプログラムを整理します。

スコープについて
externの前に変数のスコープというものを学ぶ必要があります。
基本的に変数は、ブロック内( 中かっこ{}区切られた範囲と考えてください )でしか使えません。
これがブロックスコープです。
関数funcAの変数aは関数funcBから利用できないということです。

コード:

void funcA() {
  int a = 0; // funcAのスコープの変数 
}
void funcB() {
  int b = a; // aはスコープにないのでダメ
}
関数の外側の変数がファイルスコープの変数になります。
ブロックに関わらずに利用できますが、他ファイルの変数を利用することはできません。
file1.c のファイルスコープの変数cは file2.c で利用できないということです。

externについて
上記のファイルスコープの変数を別ファイルから利用するのがextern宣言です。
コンパイラに対して、この変数は他のどこかのファイルにあるやつのことだからね と伝えます。
これによってファイルをまたいで利用出来るようになるわけです。

hide

Re: externについて

#4

投稿記事 by hide » 2年前

cppとヘッダーについて

宣言と定義、includeを理解する必要が有ります。

宣言はこれを使うよ、とコンパイラに伝えることです。
宣言されるとコンパイラは「この宣言されたやつは実体がどっかにあるんだなぁ」みたいな解釈をします。
例として、DxライブラリのDrawGraphは DxLib.h で宣言してくれているので
複数のファイルからincludeして使ったとしても、同じものを使うことになるわけですね。
先に書いたexternも宣言なので、同様に「この変数はどっかにあるんだなぁ」って感じで解釈してくれます。

対して定義は、この名前のやつの実体はこいつだぞ、とコンパイラに伝えます。
ゆえに、同じものを複数回定義することはできません。
複数回定義されてしまうとコンパイラは困ってエラーとしまうわけですね。

includeは基本的にただのコピペです。
includeの右側のファイルをあるルールに従って探してきて、そこにコピーします。
(そのルールをDxlibを始めるときに設定した記憶があるかと思います。)

では上記を参考に全てヘッダに詰め込むとどうなるか考えてみましょう。
Dxライブラリの関数の定義がDxlib.hに詰め込まれていたとしたら、#include <Dxlib.h> は使えません。
複数回これを行うと、ヘッダに詰め込まれた定義がコピペによって複数になってエラーになってしまうからですね。
利用するために 毎回全てのファイルの中でDxlibの関数を宣言することになり、プログラムが宣言で埋めつくされるでしょう。
そして同様のことが akasannさんの書いたプログラムでも起こり得ます。
loading.hに詰め込むことによって、loading.hの何かを利用するために宣言毎回書く羽目になります。
loading以外の処理も同様のことをしてしまえば、宣言の量が大変なのは想像できるかと思います。

これを防ぐために、cppとヘッダをわけてcppに定義をヘッダに宣言を書き、
宣言だけをうまくincludeでコピーしてくることでうまいこと楽するのです。
(比較的新しい言語では勝手にうまくやってくれたりします。)

全体的にラフな説明なので突っ込みどころはあるかと思いますが、ご容赦を。

あんどーなつ
記事: 171
登録日時: 2年前
連絡を取る:

Re: externについて

#5

投稿記事 by あんどーなつ » 2年前

「14歳からはじめる・・・」ですね。私も読むのに時間がかかりました。非常に勉強になって面白い本だと思います。

externの使い方なのですが... 難しいですよね。コレ、重要なんですけど詳しく解説している本はあまり多くないです。実は今読んでいる本(「14歳から・・・」)の5章にこれの解説が多少書いてあります。他の解説書とは違う切り口なのでためになります。

hideさんの解説もためになりますし、K&Rとかロベールとか良解説もたくさんありますが、ぶっちゃけすぐには理解できないと思います。英語と同じように、aってなんだ、theってなんだ、atってなんだーと問答するのではなく、解説とサンプルコードを分からなくてもいいからたくさん読んで、少しづつ理解するようにします。そうすれば1,2年後くらいにはわかります(私なんて10年かかりましたよ)。


.cppファイルと.hファイルについて、少し説明してみたいと思います。

C/C++の実行ファイル作成にはコンパイルとリンクの2工程があります。基本的に、.cppファイルは中間ファイルと対応づきます。

a.cpp b.cpp c.cpp --(コンパイル)--> a.obj b.obj c.obj --(リンク)--> out.exe

これらの中間ファイルが、どういうものかと言いますと、以下のような感じです(これはg++の結果なので、こんなイメージというのをつかんでください)
a.cpp

コード:

extern sqrt(int, int);
extern int add(int a, int b) { return a+b; }
extern float distance(float x, float y) { return sqrt(x*x, y*y); }

ex1.obj(バイナリファイル)の逆アセンブル結果(抜粋)

コード:

RELOCATION RECORDS FOR [.text]:
OFFSET           TYPE              VALUE
0000000000000045 R_X86_64_PC32     _Z4sqrtii

0000000000000000 <_Z3addii>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   89 4d 10                mov    %ecx,0x10(%rbp)
   7:   89 55 18                mov    %edx,0x18(%rbp)
   a:   8b 55 10                mov    0x10(%rbp),%edx
   d:   8b 45 18                mov    0x18(%rbp),%eax
  10:   01 d0                   add    %edx,%eax
  12:   5d                      pop    %rbp
  13:   c3                      retq

0000000000000014 <_Z8distanceff>:
  14:   55                      push   %rbp
  15:   48 89 e5                mov    %rsp,%rbp
  18:   48 83 ec 20             sub    $0x20,%rsp
  1c:   f3 0f 11 45 10          movss  %xmm0,0x10(%rbp)
  21:   f3 0f 11 4d 18          movss  %xmm1,0x18(%rbp)
  26:   f3 0f 10 45 18          movss  0x18(%rbp),%xmm0
  2b:   f3 0f 59 45 18          mulss  0x18(%rbp),%xmm0
  30:   f3 0f 2c d0             cvttss2si %xmm0,%edx
  34:   f3 0f 10 45 10          movss  0x10(%rbp),%xmm0
  39:   f3 0f 59 45 10          mulss  0x10(%rbp),%xmm0
  3e:   f3 0f 2c c0             cvttss2si %xmm0,%eax
  42:   89 c1                   mov    %eax,%ecx
  44:   e8 00 00 00 00          callq  49 <_Z8distanceff+0x35>
  49:   66 0f ef c0             pxor   %xmm0,%xmm0
  4d:   f3 0f 2a c0             cvtsi2ss %eax,%xmm0
  51:   48 83 c4 20             add    $0x20,%rsp
  55:   5d                      pop    %rbp
  56:   c3                      retq
  57:   90                      nop
  58:   90                      nop
  59:   90                      nop
  5a:   90                      nop
  5b:   90                      nop
  5c:   90                      nop
  5d:   90                      nop
  5e:   90                      nop
  5f:   90                      nop
関数sqrtはexternしていますが、定義はしていないので、他の.cppファイルの関数としてコンパイラに認識されます。すると、リロケーションテーブルという所にsqrtに対応したシンボルを用意します。さらに、

コード:

  44:   e8 00 00 00 00          callq  49 <_Z8distanceff+0x35>
この行の00 00 00 00はsqrtが分からないので仮置きしているものですが、アドレス(コードの場所)は

コード:

0000000000000045 R_X86_64_PC32     _Z4sqrtii
の45に対応していて、リンカは他のオブジェクトのsqrtシンボルと結びつけるという作業を行います。

addとdistanceはexternしていますが、定義をしているので、関数がアセンブラコードに変換されて、add, distanceに対応したシンボルがそれらのアセンブラコードの先頭に割り付けられます。


.hファイルは、このようなオブジェクトファイルを作ることではなく、複数の.cppファイルにマクロ、型宣言、関数宣言を配ることを目的としています。

このような視点でソースコードを見ていくとわかりやすいのではないかと思います。

アバター
みけCAT
記事: 6150
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: externについて

#6

投稿記事 by みけCAT » 2年前

akasann さんが書きました:次のコードに 50000000 と 30000000 を標準入力すると -14 という値が出てきます。何か別のやり方があるのでしょうか?・・・
最小公倍数がintで表現できる範囲なら、オーバーフローしないように掛け算より割り算を先に行うといいでしょう。
(最大公約数の定義よりmはGCD(m,n)で割り切れるので、情報は落ちないはずです)

コード:

int LCM(int m,int n){
	return m/GCD(m,n)*n;
}
オフトピック
この話はexternとは全く関係なさそうなので、別のトピックの方が良かった気がします。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
Dixq (管理人)
管理人
記事: 1659
登録日時: 8年前
住所: 北海道札幌市
連絡を取る:

Re: externについて

#7

投稿記事 by Dixq (管理人) » 2年前

externについてしっかりと紹介しているサイトや参考文献が少ないのはexternが不必要だからです。
正直一切使わないように心がけた方が良い設計になります。
全く使わなくても使ったときと同じ処理は実現可能ですし、使うべきではありません。
どうしても使いたければうちに龍神録プログラミングの館で使い方を紹介しているのでご覧ください。
http://dixq.net/rp
使わないちゃんとした設計の基本的な考え方はゲームプログラミングの館に書いてありますのでご覧ください。
http://dixq.net/g

アバター
Dixq (管理人)
管理人
記事: 1659
登録日時: 8年前
住所: 北海道札幌市
連絡を取る:

Re: externについて

#8

投稿記事 by Dixq (管理人) » 2年前

extern宣言はちょっと工夫するとextern宣言を書かなくても書いたことにしてどっからでも変数が呼べるようになります。
(全く推奨はしませんが)こちらにはその実装方法が書いてあります。
http://dixq.net/rp/5.html

akasann

Re: externについて

#9

投稿記事 by akasann » 2年前

皆さん解答ありがとうございました。イメージ的なのは、なんとなくですがつかむことができたと思います。
ゆっくり、本のほうを進めていき、疑問に思ったことは投稿させていただきたいと思います。新ゲームプログラミングの館のほうも、おのずと見ていきたいと思います。

おまけに関しては別のタグのほうが良かったぽいですね。これから気を付けます。

閉鎖

“C言語何でも質問掲示板” へ戻る