• 締切済み

ヘッダファイルにおける文字列リソース定義

C言語(主にVC++)で開発をしていると、よく、 const char MSG_HELLO[] = "HELLO!"; のように、ヘッダファイル中で、文字列リテラルを定義します。 自分もいつのまにかこのようにリソース定義するようになっていましたが、初期の頃から疑問だったのは、extern宣言せずにchar配列を宣言しているから、コンパイラが最適化しなかったら、上記宣言のあるヘッダファイルを読み込んで、その定義を使った個所のあるソース分文字列リテラルがメモリ上に確保されてしまうのではないか、、?ということです。 つまり、本来なら const char* MSG_HELLO[] = "HELLO!"; とソースファイル中で定義し、ヘッダファイルには extern const char* MSG_HELLO; とすべきではないか、と思うわけです。ただ実際にこれをやっているとヘッダとソースの両方のメンテナンスが必要になるので、冒頭のように記述しているのだと思いますが。 この、本来なら下記のようにすべきだが、コンパイラの最適化(リソースのプール)を期待して最初のように書いている、という解釈は正しいでしょうか?

みんなの回答

  • yatokesa
  • ベストアンサー率40% (201/496)
回答No.4

>ファイル冒頭で#ifdefで区切っても各オブジェクトファイルでオブジェクトが確保される点は変わらないと思い ます、、 #ifdef __MAIN__ const char* MSG_HELLO = "HELLO!"; #else extern const char* MSG_HELLO; #endif mainのあるソースのみ #define __MAIN__ のようにする。オブジェクトは1つで済みますよね? 私の常套手段です。が、これが増えてくるとメンテナンスが大変になってきますが...。

akubiII
質問者

お礼

なるほど、、! これはアイデアですね。 参考にさせて頂きます。 ソースとヘッダにわける以外では、最善策かもしれませんね、、悩ましいです

  • toysmith
  • ベストアンサー率37% (570/1525)
回答No.3

ANSI以前のC言語において「外部リファレンスを持つ変数オブジェクト」の領域は『あいまいなDEF/REF規約』によって規定されていました。 あいまいなDEF/REF規約とは「定義(Define)と参照(Reference)が明確に分離していない」ということで、 ・ブロック外定義の変数/配列は記憶クラスを省略するとexternとなる ・複数のextern宣言された変数/配列オブジェクトが存在する場合、1つの実態が確保される という規約によって正当化されます。 結果、ご質問のようなコードはリンカによって1つの実体オブジェクトになることが保証されていました。 しかし、現在のC言語規約では「明確なDEF/REF規約」が用いられているため、ブロック外で記憶クラスを省略した同一名称の変数/配列オブジェクトを複数宣言することは推奨されません。 「明確なDEF/REF規約」に準拠した処理系ではリンカエラーになります。 「あいまいなDEF/REF規約」をサポートした処理系ではワーニングが発生した上で、ANSI以前のCコンパイラと同様の配置を行います。 static記憶クラスで宣言していない限り、同一名称の変数/配列オブジェクトが複数配置されることは(外部リファレンスを持つオブジェクトでは)ありえません。

akubiII
質問者

お礼

なるほど、、そういう規約があったんですね。ではVCはあいまなDEF/REF規約をサポートしているんですね、、 ということはやはり最新の仕様ではヘッダファイルで文字列リテラルを定義するのは望ましくないと思うのですが、では実際どのようにすればいいのでしょう、、yatokesaさんがご指摘の通りVCではリソースファイルのストリングテーブルを使うのが望ましいかもしれませんが、プラットフォーム依存になってしまう点と、rcファイルは扱いにくい(リソースエディタを開くのも面倒)という点から、つかっていません。やはりヘッダとソースにわけるのが正しいのでしょうか、、これもメンテナンスが大変になるので避けたいのですが、、 なお質問中の const char* MSG_HELLO[] = "HELLO!"; は onst char* MSG_HELLO = "HELLO!"; と訂正させて頂きます。 ご回答ありがとうございました。

  • taka_tetsu
  • ベストアンサー率65% (1020/1553)
回答No.2

私ならリテラル文字列はヘッダファイルで #define か、リソースです。 ちなみに、C++でヘッダファイル中に const char* MSG_HELLO[] = "HELLO!"; を書くとリンクでエラーになったような・・・

akubiII
質問者

お礼

#defineで区切ると、各部に文字列リテラルが直接埋め込まれてしまうので、コンパイラの最適化がないとそれこそ大変なことになる気がして使っていません。 たしかにQuickCでやっていた頃は、ヘッダで定義するとエラーになった記憶があります。このあたりはtoysmithさんがお答えになっている通りなのでしょうね、、 ご回答ありがとうございました。

  • yatokesa
  • ベストアンサー率40% (201/496)
回答No.1

回答ではないですが... #最近のコンパイラ・リンカの事情には詳しくはないので 少なくとも、コンパイラの最適化ではないですよね。コンパイラはオブジェクトを作り、その中に "HELLO!"分の領域はどのオブジェクトにもあると思います。 リンカが最適化によって上記領域をまとめるか否かはよくわかりません。私の認識では、ロードモジュールになったときにも別の領域になっていると考えてます。#リンクマップを見ればわかるのかな? メンテナンス性という意味で、#define と #ifdefを組み合わせればそれほど苦では無いと思います。量が多ければ大変ですが...。VCなら rcに文字列をまとめた方が行儀がよいのかもしれませんね。#私はやってませんが...^^;)

akubiII
質問者

お礼

たしかにコンパイル完了時点では、各オブジェクトファイル内に各文字列オブジェクトがあり、リンク時にまとめられるのでしょうね、、私もそのあたりもう少し勉強しなければ。 ファイル冒頭で#ifdefで区切っても各オブジェクトファイルでオブジェクトが確保される点は変わらないと思います、、 ご回答ありがとうございました。

関連するQ&A

  • C言語のヘッダファイルの使い方

    ヘッダファイルの使い方について質問です。 ソースファイルA、ソースファイルBで共有して使用したい変数がある場合、 Aでは「int a」と宣言し、Bでは「extern int a」と宣言すれば 同じ変数を共有出来ると認識しています。 それをヘッダファイルへ記述しておきたい場合にはどのように 宣言しておけば良いのでしょうか? ヘッダファイルに「int a」と宣言した場合は両方のソースファイルで includeした時に多重定義でエラーとなります。 では「extern int a」と宣言しておいて、両方のソースファイルで includeするのが正しいのでしょうか? 初歩的な質問で申し訳ないですが、有識者の方、教えてください。

  • 定義されているのにエラーになる

    閲覧ありがとうございます。 C言語のプログラムの話です。 ソースファイルaaa.cでstdio.hをincludeしています。 stdio.hには extern FILE _iob[_NIOBRW]; というのが定義されているにも関わらず、 コンパイルすると、 aaa.o:aaa.c:(.data+0x58): undefined reference to '_iob' というエラーが出ます。 aaa.cでは_iobに関する宣言はしていませんが、それを使うこともしていません。 ヘッダファイルでexternで宣言してるからかなと思い、ソースファイルにexternなしの宣言を書き足したら、コンパイルは通りました。 今回は元々既にあるプロジェクトの改修だったのですが、宣言を消したりしていません。 しかし、その部分は元々ヘッダファイルに宣言などしなくてもコンパイルが通ってました。 昔はヘッダファイルでexternで宣言してたらソースファイルではしなくてオッケーみたいな感じだったんですかね? 私自身、あまりよく分かってなくて文章もめちゃくちゃですみません。 不足している情報があればできる範囲でお伝えしますので、ご協力よろしくお願いいたします。

  • 文字列の連結

    ポインタ変数で宣言された3つ以上の文字列を連結したいのです。 そこで char *str1 = "Hello,"; char *str2 = "Mr."; char *str3 = "Brown."; char msg[100]; strcat(msg,strcat(str1,strcat(str2,str3))); printf(msg); としたがやっぱり駄目でした。 strcat(str2,str3)からして駄目なんだとはなんとなくわかるのですが どうしたら解決できるのかわかりません。 結果的には printf(msg); ->Hello,Mr.Brown. としたいのです。解決案をご教授いただけないでしょうか。 お願いいたします。

  • c++11での文字列リテラルの特殊化について

    c++11言語でのテンプレート部分特殊化についての質問です。 コメントアウト部分は出力結果です template<class T> struct VT { static const int type = 1;}; template<class T,int N> struct VT< T[N] > { static const int type = 2;}; template<class T,int N> struct VT< const T[N] > { static const int type = 3;}; template<class T> struct VT< T* > { static const int type = 4;}; template<class T> struct VT< const T*const > { static const int type = 5;}; #include<iostream> #include<typeinfo> int main(){ std::cout<<"A:"<< VT< char >::type << std::endl; // A:1 std::cout<<"B:"<< VT< char[10] >::type << std::endl; // B:2 std::cout<<"C:"<< VT< char* >::type << std::endl; // C:4 std::cout<<"D:"<< VT< char const [1] >::type << std::endl; // D:3 std::cout<<"E:"<< VT< decltype("") >::type << std::endl; // E:1 std::cout<<"G:"<< typeid( char const [1] ).name() << std::endl;// G:char const [1] std::cout<<"H:"<< typeid( "" ).name() << std::endl;// H:char const [1] } 型名を直接記述したD,G、文字列リテラルを記述したE,H。 コンパイラ毎の差はあれど、GとHの型名は同じものが表示されます。 ですが、[D:3] [E:1]と値は違い、別の特殊化テンプレートが使われています。 この部分が分かりません。 また、配列リテラル、文字列リテラルに対し部分特殊化テンプレートを宣言する方法などありましたら、ご教示お願いします。

  • 関数の実体定義にヘッダファイルの2重定義防止方法が効かない?

    いつもお世話になっています。 MFCでCプログラミングをしています。 ヘッダファイルの2重定義防止のために、 ヘッダファイル全体を下記のように 囲みました。 <aaa.h> #ifndef AAA #define AAA #define PI 3.141592 void Func(); int wa(int a, int b){ return a+b; } #endif ビルドしたところ、 関数宣言(Func)や#define部分(PI)については、 2重定義が防止されているようなのですが、 関数の実体部分(関数wa)については、 2重定義防止機能が働かず、 ***.obj : error LNK2005: "int __cdecl wa(int a, int b)" は既に ***.obj で定義されています。 というリンクエラーが表示されます。 関数の種類や ヘッダファイル内の宣言の順番を いろいろ変えてみたのですが同じ結果でした。 ここで、このヘッダファイルの先頭に #pragma onceを使用すると このリンクエラーは回避されるのですが、 他コンパイラとの互換性の観点から、 #pragma once以外の方法で実現する必要があるので、 困っています。 URLを検索してみたのですが、 このような特殊な場合について記述されているものは 見つけられませんでした。 どなたか解決法又はヒントをご教示頂ければ ありがたいです。 よろしくお願いします。

  • ファイルポインタのヘッダーファイルの配置について

    分割コンパイルをしている別ファイルの別関数から、同じファイルポインタ(main関数でfopen済み)に出力(fprintf)は可能でしょうか? 可能な場合、 1.ファイルポインタをexternで宣言 2.ファイルポインタをヘッダファイル内で宣言 3.その他方法 どの方法で実現できるのでしょうか? サンプルコード等書いていただけたら助かります。 よろしくお願い致します。

  • ヘッダーファイルについて

    //DMusic7Ex.h あるプログラムを解析しています。 //コンパイラ設定 //多重定義防止 #pragma once //ファイルインクルード #include <dmusicc.h> #include <dmusici.h> //型定義 typedef IDirectMusicPerformance* LPDIRECTMUSICPERFORMANCE; typedef IDirectMusic* LPDIRECTMUSIC; typedef IDirectMusicLoader* LPDIRECTMUSICLOADER; typedef IDirectMusicSegment* LPDIRECTMUSICSEGMENT; //関数プロトタイプ 1・DMusic7Ex.cppを作らず他の関数で、このヘッダーファイルをインクルードする手法は一般的なのか? 2・なぜ関数プロトタイプを記述しないで、このタイプ宣言を他のファイルで使用するのは一般的なのか?

  • Cコンパイル時のマルチデファイン優先

    Cコンパイル時にincludeファイルの都合上、マルチデファインになってしまいます。 この点はコンパイラが宣言無視をしてくれるのですが、extern優先にしてリンクをするにはどのようにすれば良いのでしょうか。 ファイル内でinclude文(ファイル内)定義より先にextern文でシンボル定義をすれば、extern優先でリンクしてくれるのでしょうか(試して無いのですが)。 コンパラ・リンカ製品の仕様になるのでしょうけれど「マルチデフは絶対ダメ」なコンパイラでない場合、extern宣言を優先にする一般的な方法はありますか 。

  • クラスのメンバ関数を別ファイルで定義したときのバグ

    C++ においてヘッダファイルで宣言したクラスのメンバ関数を別のソースファイルで定義して、コンパイルするとうまくいきません。エラーは出ないのですが、同名の何もしない関数としてコンパイルされているようなのです。クラスのメンバ関数を宣言したのと同じヘッダに書くとちゃんとコンパイルされます。 どうしてそうなるのか、いまいち原因がわかりません。

  • Const定義した文字列にパラメータを渡す?

    いつもお世話になっています。 今開発中のプロジェクトでメッセージ文言の文字列をインクルードファイルにconstで定義し、実際のプログラム中にベタで書くのをやめる決まりを作ろうと思うのですが、const定義した文字列にパラメータを渡すことが出来ますか? たとえば const A = "すでに同じ%があります。" Aの"%"部分を可変にしたい場合、たしか上記のように"%"か"?"かなんかで設定してあげて、文字列を編集したメッセージボックスを表示する共通関数を作り、その関数に%部分の文字列をパラメータで渡すという開発手順をVBで経験した記憶があるのですが・・・。 その時は共通関数を呼ぶだけで、まだ超初心者だったこともあり関数の中身まで読んで勉強することがなかったのでよく仕組みがわからないのですが、こういう場合、自分で文字列置換の関数を作るだけなんですか? それともなんらかの方法があるのでしょうか。 みなさんよろしくお願いいたします。