m_nukazawaの日記: ゆるぼ:C言語でネストした構造体の初期値定義がしたい 7
C言語の構造体には、C++のようなコンストラクタがありません。
コンストラクタは、構造体オブジェクトの初期値を定義し、初期化を保障する機能を持っています。
C言語で構造体の初期化を保証するのは諦めるとして、初期値を定義しておきたい。
なので現在は、ヘッダファイル上にグローバルでstatic constな初期値オブジェクトを作り、構造体オブジェクト作成時に代入することで、初期値を定義しています。
(サンプルコード参照)
しかし、static constな構造体は、ネストに対応していないようです。
(エラーになってからよくよく考えてみれば、納得できなくもありませんが。)
つまり、下記サンプルコードのようなことがしたい。
``` et_snap_context.h
12 ¬
13 typedef struct{¬
14 >-------bool is_snap_for_grid;¬
15 }EtSnapContext;¬
16 ¬
17 static const EtSnapContext EtSnapContext_Default = {¬
18 >-------.is_snap_for_grid = false,¬
19 };¬
20 ¬
```
``` et_document_preference.h
12 ¬
13 #include "et_snap_context.h"¬
14 ¬
15 typedef struct{¬
16 >-------EtSnapContext snap_context;¬
17 }EtDocumentPreference;¬
18 ¬
19 static const EtDocumentPreference EtDocumentPreference_Default = {¬
20 >-------.snap_context = EtSnapContext_Default,¬
21 };¬
```
いま思いついている他の手法は以下。
・初期値定義にマクロを使う方法は、改行に'\'、ブラケットを正しく書くのが難しい等、読み書きがつらい
・初期化関数をヘッダに持つ方法は、gccの未使用関数の警告オプションに引っかかってしまう
・ヘッダに初期化関数または初期値オブジェクトの宣言だけ書き、定義は.cファイルに書く方法は、ヘッダとソースに構造体の情報が分かれるので、一覧性が低く、構造体と初期値の定義という目的に対して大げさ。
・``` = {0}; ```で代入される値をすべての構造体の初期値として定義する。
(メッセージフォーマットならともかく、)is_exist_*とis_not_exist_*が入り交じる構造体メンバ定義は正気の沙汰ではない。
// スタックオーバーフローに投げようかとも思ったのですが、とりあえずの整理も兼ねて日記に。
良い案、あるいはわたしの思い違い等がありましたら、ご指摘頂ければ幸いです。
ネストに対応していないというより (スコア:2)
ネストに対応していないというより、グローバル変数や static な変数の初期化に、(例え static constでも)変数が使えない感じですかね。
初期化関数をヘッダに持つ方法で、static inline としておくと、私の環境では、gccで警告はでないです(理由は知らない)。
ちなみに、初期化関数って、こういうのでいいですか?
static inline EtSnapContext EtSnapContextDefault(void){
return (EtSnapContext){.is_snap_for_grid=1};
}
自分なら malloc して使う前提の構造体なら、ヘッダに et_snap_context*et_snap_context_new(void);とか宣言して、.cにメモリ確保と初期化を実装します。
そうでなければ、et_snap_context_init(et_snap_context*p); みたいなのを作ります。
struct point{int x; int y};のような単純な構造体であれば、マクロをつかうこともあります。
初期値の一覧性って重要ですかね?
svn-init() {
svnadmin create .svnrepo
svn checkout file://$PWD/.svnrepo .
}
Re:ネストに対応していないというより (スコア:2)
> static inline
inlineにそんな効果があるということなのでしょうか。少し謎ですね。
> mallocして使うなら
メモリ確保して使う構造体の場合は、私も確保&初期化関数を書いています。
ただ、すべてをそうしてしまうと、開放の管理が大変なので、中規模なデータ等、可能であれば関数ローカル変数として作ったり、値渡しを利用することが多いです。
> 初期値の一覧性
せいぜい一箇所でしか初期化しない、そこに初期値を書けばいい、という場合はけっこう多いといえば多いのですが、そうでない場合もあったりするのです。
例えば、SVGオブジェクトの初期色は、SVGパーサ中にまあまあ出て来るようです。色情報の構造体は、真面目にやるとRGBだけの単純構造では不十分で、構造体入りの構造体が欲しくなります。
静的変数の初期値はリテラルのみ (スコア:1)
C言語の場合、静的変数の初期値はコンパイル時に確定する必要があるので、リテラルでないものは初期値には指定できません。
auto変数の場合は、初期値は実行時に設定されるので、変数でも関数でも指定できます。
マクロで初期値とする複合リテラルを宣言するのがいちばん素直な方法だと思います。
マクロであればネストしてもかまいませんし。
マクロ宣言で行末にバックスラッシュを書くことなどたいした問題ではないし、コード本文で初期値を正しく書けるのであれば、マクロ宣言でも正しく書けるはずです。
マクロの展開結果が不安なら、cc -E で確認できます。
どうしてもマクロで書くのが嫌なら、gcc の場合、__attribute__((constructor)) をつけて関数宣言すれば、main の実行の前にこの関数が実行されます。
グローバル変数であれば、ここで初期化することはできます。
(関数内 static はスコープ上無理。)
あるいは、処理系がC++11に対応していれば、constexpr 指定でコンパイラが計算してくれるため、初期値として constexpr 宣言した変数も指定できます。
ただし、これは C++ としての機能であるため、ファイルの拡張子を .cpp などにして、中身は mangle させないように extern "C" { }; で前後をくくっておくようにしておく必要があります。
Re:静的変数の初期値はリテラルのみ (スコア:2)
やはりCならマクロが良いのかもしれません。そんな気もしてきました。
C++を使ってconstexprは思いつきませんでした。
Re: (スコア:0)
gccはC++モードでもdesignated initializerが使えるのでしたっけ? (素直にC++にしないのはdesignated initializerが使いたいからだとサンプルコードから判断していました)
ほい (スコア:0)
struct A {
int x;
};
struct B {
struct A a;
};
#define aaa (struct A){.x=123}
#define bbb (struct B){.a = aaa}
int
main()
{
struct B b = bbb;
}
Re:ほい (スコア:2)
define、上記サンプルコードでは良くても、メンバ変数が増えると改行等が大変なのでは。