Dxライブラリの本をゆっくりと読みながら4章終わりまで来ました。やってて、ゲームプログラミングの流れというものは、なんとなくですがわかりました。いくつか疑問になった点があるので、質問したいと思います。
1.件名の通りextern宣言の使い方がいまいち理解できません。ネットで調べてみると、モジュールが・・・関係?していることが分かったんですが・・・色々教えていただけるとありがたいです。モジュールは実行ファイルという形で理解しているのですが、あっているのでしょうか?
2.疑問に思ったこととして、cppファイルとヘッダーファイルを二つ用意しているところが不思議に思いました。例えば、画像を読み込むところとしてloading.cppとloading.hを用意しています。loading.hだけではダメなのでしょうか?質問の意味が理解できるか分りませんがお応えしてくれるとうれしいです。
おまけとして、AOJの問題にも取り組んでいるのですが、前のDight Numberについては解決しました。次の問題として最小公倍数の問題に取り組んでいます。次のコードに 50000000 と 30000000 を標準入力すると -14 という値が出てきます。何か別のやり方があるのでしょうか?・・・
externについて
Re: externについて
50000000 と 30000000 をかけ算している時点で、int型で扱える数値の範囲を超えているような気がする。akasann さんが書きました: おまけとして、AOJの問題にも取り組んでいるのですが、前のDight Numberについては解決しました。次の問題として最小公倍数の問題に取り組んでいます。次のコードに 50000000 と 30000000 を標準入力すると -14 という値が出てきます。何か別のやり方があるのでしょうか?・・・
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。
プログラムは思ったとおりには動かない。書いたとおりに動く。
Re: externについて
モジュールについて
定義された用語としてちゃんとあるのかは調べていませんが、
おそらくこの場合は機能ごとに分割されたプログラムの一部、でいいでしょう。
ゲームプログラムだったら、プレイヤーモジュールとかマップモジュールとかでしょうね。
これらをそれぞれ別のファイルに書くことによって下記のスコープ等を分けてプログラムを整理します。
スコープについて
externの前に変数のスコープというものを学ぶ必要があります。
基本的に変数は、ブロック内( 中かっこ{}区切られた範囲と考えてください )でしか使えません。
これがブロックスコープです。
関数funcAの変数aは関数funcBから利用できないということです。 関数の外側の変数がファイルスコープの変数になります。
ブロックに関わらずに利用できますが、他ファイルの変数を利用することはできません。
file1.c のファイルスコープの変数cは file2.c で利用できないということです。
externについて
上記のファイルスコープの変数を別ファイルから利用するのがextern宣言です。
コンパイラに対して、この変数は他のどこかのファイルにあるやつのことだからね と伝えます。
これによってファイルをまたいで利用出来るようになるわけです。
定義された用語としてちゃんとあるのかは調べていませんが、
おそらくこの場合は機能ごとに分割されたプログラムの一部、でいいでしょう。
ゲームプログラムだったら、プレイヤーモジュールとかマップモジュールとかでしょうね。
これらをそれぞれ別のファイルに書くことによって下記のスコープ等を分けてプログラムを整理します。
スコープについて
externの前に変数のスコープというものを学ぶ必要があります。
基本的に変数は、ブロック内( 中かっこ{}区切られた範囲と考えてください )でしか使えません。
これがブロックスコープです。
関数funcAの変数aは関数funcBから利用できないということです。 関数の外側の変数がファイルスコープの変数になります。
ブロックに関わらずに利用できますが、他ファイルの変数を利用することはできません。
file1.c のファイルスコープの変数cは file2.c で利用できないということです。
externについて
上記のファイルスコープの変数を別ファイルから利用するのがextern宣言です。
コンパイラに対して、この変数は他のどこかのファイルにあるやつのことだからね と伝えます。
これによってファイルをまたいで利用出来るようになるわけです。
Re: externについて
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でコピーしてくることでうまいこと楽するのです。
(比較的新しい言語では勝手にうまくやってくれたりします。)
全体的にラフな説明なので突っ込みどころはあるかと思いますが、ご容赦を。
宣言と定義、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でコピーしてくることでうまいこと楽するのです。
(比較的新しい言語では勝手にうまくやってくれたりします。)
全体的にラフな説明なので突っ込みどころはあるかと思いますが、ご容赦を。
Re: externについて
「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
↓
ex1.obj(バイナリファイル)の逆アセンブル結果(抜粋)
関数sqrtはexternしていますが、定義はしていないので、他の.cppファイルの関数としてコンパイラに認識されます。すると、リロケーションテーブルという所にsqrtに対応したシンボルを用意します。さらに、
この行の00 00 00 00はsqrtが分からないので仮置きしているものですが、アドレス(コードの場所)は
の45に対応していて、リンカは他のオブジェクトのsqrtシンボルと結びつけるという作業を行います。
addとdistanceはexternしていますが、定義をしているので、関数がアセンブラコードに変換されて、add, distanceに対応したシンボルがそれらのアセンブラコードの先頭に割り付けられます。
.hファイルは、このようなオブジェクトファイルを作ることではなく、複数の.cppファイルにマクロ、型宣言、関数宣言を配ることを目的としています。
このような視点でソースコードを見ていくとわかりやすいのではないかと思います。
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
addとdistanceはexternしていますが、定義をしているので、関数がアセンブラコードに変換されて、add, distanceに対応したシンボルがそれらのアセンブラコードの先頭に割り付けられます。
.hファイルは、このようなオブジェクトファイルを作ることではなく、複数の.cppファイルにマクロ、型宣言、関数宣言を配ることを目的としています。
このような視点でソースコードを見ていくとわかりやすいのではないかと思います。
Re: externについて
最小公倍数がintで表現できる範囲なら、オーバーフローしないように掛け算より割り算を先に行うといいでしょう。akasann さんが書きました:次のコードに 50000000 と 30000000 を標準入力すると -14 という値が出てきます。何か別のやり方があるのでしょうか?・・・
(最大公約数の定義よりmはGCD(m,n)で割り切れるので、情報は落ちないはずです)
オフトピック
この話はexternとは全く関係なさそうなので、別のトピックの方が良かった気がします。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)
- Dixq (管理人)
- 管理人
- 記事: 1661
- 登録日時: 13年前
- 住所: 北海道札幌市
- 連絡を取る:
Re: externについて
externについてしっかりと紹介しているサイトや参考文献が少ないのはexternが不必要だからです。
正直一切使わないように心がけた方が良い設計になります。
全く使わなくても使ったときと同じ処理は実現可能ですし、使うべきではありません。
どうしても使いたければうちに龍神録プログラミングの館で使い方を紹介しているのでご覧ください。
http://dixq.net/rp
使わないちゃんとした設計の基本的な考え方はゲームプログラミングの館に書いてありますのでご覧ください。
http://dixq.net/g
正直一切使わないように心がけた方が良い設計になります。
全く使わなくても使ったときと同じ処理は実現可能ですし、使うべきではありません。
どうしても使いたければうちに龍神録プログラミングの館で使い方を紹介しているのでご覧ください。
http://dixq.net/rp
使わないちゃんとした設計の基本的な考え方はゲームプログラミングの館に書いてありますのでご覧ください。
http://dixq.net/g
- Dixq (管理人)
- 管理人
- 記事: 1661
- 登録日時: 13年前
- 住所: 北海道札幌市
- 連絡を取る:
Re: externについて
extern宣言はちょっと工夫するとextern宣言を書かなくても書いたことにしてどっからでも変数が呼べるようになります。
(全く推奨はしませんが)こちらにはその実装方法が書いてあります。
http://dixq.net/rp/5.html
(全く推奨はしませんが)こちらにはその実装方法が書いてあります。
http://dixq.net/rp/5.html
Re: externについて
皆さん解答ありがとうございました。イメージ的なのは、なんとなくですがつかむことができたと思います。
ゆっくり、本のほうを進めていき、疑問に思ったことは投稿させていただきたいと思います。新ゲームプログラミングの館のほうも、おのずと見ていきたいと思います。
おまけに関しては別のタグのほうが良かったぽいですね。これから気を付けます。
ゆっくり、本のほうを進めていき、疑問に思ったことは投稿させていただきたいと思います。新ゲームプログラミングの館のほうも、おのずと見ていきたいと思います。
おまけに関しては別のタグのほうが良かったぽいですね。これから気を付けます。