C言語関係掲示板

過去ログ

No.1067 可読性を犠牲にした構造体の初期化の利点は?

[戻る] [ホームページ]
No.13967

この構造体のメンバ初期化の方法に利点はあるの?
投稿者---たけし(2004/05/12 15:39:55)


先月より派遣で、某通信ソフト会社で開発している者です。
以下のような、ソースコードの保守を任され、毎日頭を抱えています。
この構造体のメンバ初期化の方法は、可読性を犠牲にしても、何らかの
利点があって行っているのでしょうか?お教え願います。

#define UCHR unsigned char

typedef struct {
    UCHR  elmid;
    UCHR  elmlen;
    UCHR* elmadr;
} L3SELM;

typedef struct {
    UCHR   proid;
    UCHR   msgid;
    UCHR   errno;
    UCHR   elmcnt;
    UCHR   code5ptr;
    UCHR   code6ptr;
    UCHR   code7ptr;
    UCHR   codeFptr;
    L3SELM elm[20];
} L3SINF;

L3SINF l3rminf;

int main()
{
    UCHR i,j;

    j = sizeof(L3SINF) - (sizeof(L3SELM) * 20);
    for(i = 0; i < j; i++)
    {
        ((UCHR*)&l3rminf)[i] = 0x00;
    }
    return 0;
}



No.13969

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---たか(2004/05/12 15:55:32)


>この構造体のメンバ初期化の方法は、可読性を犠牲にしても、何らかの
>利点があって行っているのでしょうか?お教え願います。

UCHR proid;
UCHR msgid;
UCHR errno;
UCHR elmcnt;
UCHR code5ptr;
UCHR code6ptr;
UCHR code7ptr;
UCHR codeFptr;

これだけのメンバに0x00を代入しているようです。パディングが起きず
なおかつメンバの並び替えが起きなければいいのかもしれませんが、
移植性はないでしょう。正確にはoffsetofマクロを使用すべきですね。


No.13970

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---たけし(2004/05/12 16:54:03)


>UCHR proid;
>UCHR msgid;
>UCHR errno;
>UCHR elmcnt;
>UCHR code5ptr;
>UCHR code6ptr;
>UCHR code7ptr;
>UCHR codeFptr;
>
>これだけのメンバに0x00を代入しているようです。パディングが起きず
>なおかつメンバの並び替えが起きなければいいのかもしれませんが、
>移植性はないでしょう。正確にはoffsetofマクロを使用すべきですね。

私は派遣なので、言われるがままコンパイルまでを、Win XP,VC++6 で行っております。
ファームウェアなのですが、動作環境までは教えて頂いておりません。
パディングやメンバの並び替えはコンパイラが自動的に行うものですよね。
ファームウェア開発は、今回が初めてですので、とても心配です。


No.13972

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---NykR(2004/05/12 17:44:40)


>パディングやメンバの並び替えはコンパイラが自動的に行うものですよね。

Cでは、メンバの並べ替えは行われません。
また、構造体の最後のメンバが構造体の場合、(アラインメントの問題は片が付いているはずなので)その後ろにパディングが入ることはないような気がしないでもないこともないではないです。が、不安だったら、たかさんのいうように、offsetofマクロを使った方がいいでしょう。


No.13973

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---nop(2004/05/12 18:11:48)


>offsetofマクロを使った方がいいでしょう。

むしろ、その構造体型の初期化用の変数を定義して、構造体の代入をした方が確実でしょう。


[例]
struct test
{
  int  a;
  int  b;
};

struct test  TestVal;

int  main( void )
{
    struct test  work = {0};

    /* ----- TestVal の初期化 ----- */
    TestVal = work;

    return 0;
}



No.13974

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---たか(2004/05/12 18:23:28)


>Cでは、メンバの並べ替えは行われません。
>また、構造体の最後のメンバが構造体の場合、(アラインメントの問題は片が付いているはずなので)その後ろにパディングが入ることはないような気がしないでもないこともないではないです。が、不安だったら、たかさんのいうように、offsetofマクロを使った方がいいでしょう。

そうでした。並び替えはしないんだった・・・しかしそれでもメンバ名
以外の方法で構造体をアクセスするのは基本的には避けたいですね。

ここここのような事をしなければ大丈夫です。

特に後者のようなテクニックは便利なので多用される傾向にありますし、
それから二つ以上の異なる構造体やPODの実体確保を一回のmalloc()で済
ますとか、いろいろ汚いテクニックが現場では好まれるのですが、私は
好きではありません。

まだコード領域とデータ領域の区別がなかった頃のパソコンでは、何と
関数の中身を入れ替えるという強烈なテクニックが使われていたそうで
す。メモリが少ないし仕方なかったんですかね。話が脱線しました。


No.13994

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---nm(2004/05/13 03:00:58)


>まだコード領域とデータ領域の区別がなかった頃のパソコンでは、何と
>関数の中身を入れ替えるという強烈なテクニックが使われていたそうで
>す。メモリが少ないし仕方なかったんですかね。話が脱線しました。

脱線ついでに…
今でも、JavaVMなど、JIT(Just In Time)コンパイラなんかを搭載した実行
システムでは、データ領域(通常のヒープ)にコンパイルしたコードを実行時に
書き込んで、そのデータ領域を実行するということをやります。
なので、当然、実行時にコードを書き換えるなんて芸当は(関数全体でも、
一部でも)いくらでも出来ますし、実際に行われてます。


No.13997

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---たか(2004/05/13 12:33:59)


>脱線ついでに…
>今でも、JavaVMなど、JIT(Just In Time)コンパイラなんかを搭載した実行
>システムでは、データ領域(通常のヒープ)にコンパイルしたコードを実行時に
>書き込んで、そのデータ領域を実行するということをやります。
>なので、当然、実行時にコードを書き換えるなんて芸当は(関数全体でも、
>一部でも)いくらでも出来ますし、実際に行われてます。

まあそうなんですが、その場合はデータ領域にJITコンパイラが生成し
たネイティブコードを書き込み、それを実行するという形を取るわけです
よね。ウィルスなんかはスタック領域のコードを実行します(ぉぃぉぃ)

私の言っている関数ごと書き換えというのは次のようなコードを指して
言っています。このような事が実際に製品で使われていたそうです。
コード領域が書き込み禁止属性が付いていると当然動きません。そして
実際のプログラムはfunc2()をディスク上にファイルとして置いていた
というから恐れ入ります。(この後オーバーレイという概念が発達し、
実行時にコードを取り替えるのは珍しい事ではなくなりました)

nmさんのおっしゃられる事とはちょっとニュアンスが違うんです。言語自体
が全く実行時書き換えをサポートしてない時の話なんです。

#include <stdio.h>

void func1(void);
void func2(void);

int main(void)
{
  int i, n;
  char *c, *d;
  
  func1();
  
  c = (char *)func1; d = (char *)func2;
  n = d - c;
  
  for (i = 0; i < n; i++)
    *c++ = *d++;
  
  func1(); /* func2のコピーが実行される */
  
  return 0;
}

void func1(void)
{
  puts("func1()");
}

void func2(void)
{
  puts("func2()");
}



No.13998

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---たか(2004/05/13 12:43:48)


×この後オーバーレイという概念が発達し、
○この後オーバーレイの機能がCコンパイラにどんどん組み込まれ、


No.14002

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---たか(2004/05/13 19:16:33)


話のついでですが、このHPにいらっしゃる方の中にはゲームメーカーに
勤めておいでの方もいらっしゃるでしょう。

ゲーム機はゲームに特化して作られているので画像やサウンド機能は充実
していますが、搭載メモリが意外と小さいのはご存じだと思います。です
から次々にCDROMやDVDROMからコード片をロードして実行していく仕組みに
なっています。

この考え方はオーバーレイというより、私が上げた関数毎取り替えると
いう考え方に近いと思いませんか?もちろんOSがサポートしている事
ですのであんなに危険ではないです。


No.14012

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---nm(2004/05/14 00:34:53)


>nmさんのおっしゃられる事とはちょっとニュアンスが違うんです。言語自体
>が全く実行時書き換えをサポートしてない時の話なんです。

このニュアンスの違いというのが理解できません。
実行時書き換えというレベルでは、もはや言語とは無関係なレベルの話だと
思いますが…
関数そのものを動的に用意することも、その一部or全部を入れ替えることも、
本質的に何も変わりませんよ。
というか、アセンブラレベルでいろいろやる場合には、そもそも関数という
考え方(要は呼び出して、必ず呼び元に返ること)から自由になって、ただの
分岐で好きなところへ飛べるわけですから。
(JavaVMでは実際に、そういうトリッキーなことをいくらでもやってるはずです)

何か不具合がある場合に「パッチを当てる」ということをよく行いますが、
そもそもこの言葉はメインフレーム上で動いたままのプログラムの一部を動的に
外部から書き換える、という行為から来た言葉ですし。


No.14022

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---たか(2004/05/14 14:33:38)


>実行時書き換えというレベルでは、もはや言語とは無関係なレベルの話だと
>思いますが…
>関数そのものを動的に用意することも、その一部or全部を入れ替えることも、
>本質的に何も変わりませんよ。
>というか、アセンブラレベルでいろいろやる場合には、そもそも関数という
>考え方(要は呼び出して、必ず呼び元に返ること)から自由になって、ただの
>分岐で好きなところへ飛べるわけですから。

失礼しました。これ以上何も言いますまい。


No.14013

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---シャノン(2004/05/14 00:40:46)


ますます脱線してしまいますが、最近でもそんな記憶がありますね。
VB6.0 で、GetProcAddress で取得した関数ポインタで関数を呼び出す場合。
Byte の配列に機械語のジャンプ命令を模したデータと関数ポインタを入れ、その配列の先頭をコールバック関数に見立てて Win32 API 関数に渡してやるという荒業(笑
とても使う気にはなれませんでした…


No.14014

Re:この構造体のメンバ初期化の方法に利点はあるの?
投稿者---nm(2004/05/14 00:47:21)


>VB6.0 で、GetProcAddress で取得した関数ポインタで関数を呼び出す場合。
>Byte の配列に機械語のジャンプ命令を模したデータと関数ポインタを入れ、その配列の先頭をコールバック関数に見立てて Win32 API 関数に渡してやるという荒業(笑

大抵のUnix環境だと、関数ポインタは関数の実体の先頭アドレスを指すものでは
なくて、関数デスクリプタという数wordsの領域を指しています。
この関数デスクリプタには、実際の関数の先頭アドレスを含む、いくつかの情報
があるので、関数ポインタによる関数callは、関数デスクリプタから実体の先頭
アドレスをloadして、そこに間接ジャンプするという実装になっています。
なので、関数実体を書き換えることなく、飛び先だけ書き換えてやれば
(デスクリプタの中身だけ変える)、違う関数へジャンプさせることが出来るはず
です^^
ただし、関数デスクリプタがコード領域にないのが条件になりますが…
まあ、思いっきり環境依存ですが、やろうと思えばいろいろ出来ますよね(笑)