C言語補足で構造体・列挙・共用体の間違いやすいポイントを詳しく解説

目次

I.はじめに

2. テキスト

2.1 構造

2.11 構造の基礎知識

2.12 構造メモリの調整 (一般的なテスト ポイント)

構造メモリのアライメント規則:

2.13 構造メモリのアライメントが必要なのはなぜですか?

2.14 デフォルトのアライメント番号を変更する

2.15 関数パラメータの受け渡し

2.16ビットセグメント

2.17 ビットセグメントにおけるクロスプラットフォームの問題

2.2 列挙

2. 21 列挙の利点

2.3 コンソーシアム

3. 結論


I.はじめに

    このセクションでは、友人に利益をもたらすことを期待して、構造、列挙、および関連付けに関する知識を友人と共有します。

2. テキスト

2.1 構造

2.11 構造の基礎知識

      構造体とはメンバー変数と呼ばれる値の集合です。構造体の各メンバーは、異なる型の変数にすることができます。
構造体の宣言
構造 体タグ
{
     メンバー リスト ; _
変数 リスト; _ _
たとえば、学生について説明すると次のようになります。
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//分号不能丢

特別なステートメント (匿名のステートメント)
//匿名结构体类型
struct
{
     int a;
     char b;
     float c;
}x;
struct
{
    int a;
   char b;
   float c;
}a[20], *p;

 匿名構造体は作成時の変数に対してのみ有効であることに注意してください。x、a[20]、*p は匿名であり、後で新しい構造体変数を作成するために匿名構造体を使用することはできません。

2.12 構造メモリの調整 (一般的なテスト ポイント)

次に、構造体のメモリ サイズを       詳しく見てみましょう。

//计算出结构体内存大小
struct S1
{
   char c1;  //1字节
   int i;        // 4字节
   char c2;  //  1字节
};
// 结果: 12个字节

どうしてこれなの?

ここで調べてみましょう

構造メモリのアライメント規則:

  • 1.最初のメンバーは、構造体変数からのオフセットが0であるアドレスにあります。
  • 2.他のメンバ変数は、ある数値(アライメント番号)整数倍のアドレスにアライメントされている必要があります
Alignment = コンパイラのデフォルトのアライメントとメンバー サイズの 小さい値
VS のデフォルト値は 8 で、Linux のデフォルトのアライメント番号はそれ自体です。たとえば、次のようになります。 int アライメント番号は 4
  • 3.構造体の合計サイズは、最大アライメント番号の整数倍です (各メンバー変数にはアライメント番号があります)。
  • 4.構造がネストされている場合、ネストされた構造はその最大アライメント数の整数倍にアライメントされ構造全体のサイズは最大アライメント数 (ネストされた構造のアライメント数を含む) の整数倍に等しくなります。

 ここで前の例を分析します。

 それを検出するにはどうすればよいですか?

ここでは、マクロオフセットを   紹介します。その機能は、構造体のメンバーが配置されているメモリのオフセットを返すことです。

ヘッダー ファイル: #include<stddef.h>

 使用:

#include<stddef.h>

#include<stdio.h>

struct S3
{
    double d;// 对齐数 8
    char c; //  对齐数1
    int i; //   对齐数4
}s3;

int  main()

{

   printf("%u\n", offsetof(struct S4, c1));    //  0
    printf("%u\n", offsetof(struct S4, s3));   //   8
    printf("%u\n", offsetof(struct S4, d));     //    12
return 0;

}

上の例では最初の 3 つの原則のみを実践していますが、入れ子構造の別の例を見てみましょう。

//练习4-结构体嵌套问题
struct S3
{
  double d;  // 对齐数 8
  char c;      //  对齐数1
  int i;          //   对齐数4
};
//  S3 的大小: 16个字节
struct S4
{
  char c1;    
  struct S3 s3;  // 对齐数 8 , 16字节
  double d;       //  对齐数8
};
printf("%d\n", sizeof(struct S4));
//  结果:  32

 

図に示すように:

 2.13 構造メモリのアライメントが必要なのはなぜですか?

その多くは次のように説明されています。
  • 1.プラットフォーム理由移植理由
すべてのハードウェア プラットフォームが任意のアドレスの任意のデータにアクセスできるわけではありません。一部のハードウェア プラットフォームは、特定のアドレスで特定の種類のデータしかフェッチできず、そうでない場合はハードウェア例外がスローされます。
  • 2.パフォーマンス上の理由:
データ構造 ( 特にスタック )は 、できる限り自然な境界上に配置する必要があります。その理由は、una​​ligned にアクセスするためです。
メモリを使用する場合、プロセッサは 2 回のメモリ アクセスを必要としますが、アライメントされたメモリ アクセスでは 1 回のアクセスのみが必要です。

一般に:
構造の記憶の調整は、 空間 と時間 を交換することです

 では、どうすればスペースの使用量を削減できるのでしょうか?

狭いスペースにいるメンバーはできるだけ集まるようにしましょう。

2.14 デフォルトのアライメント番号を変更する

#pragma前処理ディレクティブについては以前に        説明しました が、ここではそれを再度使用してデフォルトの配置を変更します。

好き: 


#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;  //  1   8 -> 1
    int i;        //   4  8->  4
    char c2;  //  1   8->  1
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)  //设置默认对齐数为8
struct S2
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
   //输出的结果是什么?
   printf("%d\n", sizeof(struct S1));
   printf("%d\n", sizeof(struct S2));
   return 0;
}

 

 #pragma Pack() によって変更されるデフォルトのアライメント番号は通常 2 ^ n であり、奇数が存在することはほとんどないことに注意してください。

2.15 関数パラメータの受け渡し

     関数がパラメータを渡すとき、パラメータをスタックにプッシュする必要があるため、時間と空間のシステム オーバーヘッドが発生します。構造体オブジェクトが渡される場合、構造体が大きすぎるため、パラメータをスタックに プッシュするシステムのオーバーヘッドが比較的大きくなり、パフォーマンスの低下 につながります 。 
結論は:
構造体をパラメータとして渡す場合は、構造体のアドレスを渡す必要があります。

2.16ビットセグメント

      構造が完成したら その構造がビット セグメントを実現できるかどうかについて話さなければなりません。
ビットセグメントとは何ですか
      ビット フィールドの宣言と構造は似ていますが、次の 2 つの違いがあります。
1. ビットフィールドのメンバーは、 int unsigned int または signed intでなければなりません
2. ビットフィールドのメンバー名の後にコロンと数字があり、占有ビットを示します。
例えば:
struct A
{
   int _a:2;   // 2 bit
   int _b:5;   // 5bit
   int _c:10;
   int _d:30;
};  // 问那  struct A的大小是多少?结果是  8个字节

 

ビットセグメントメモリの割り当て
1. ビットフィールドのメンバーは 、int、unsigned int、signed int、 または char (整数ファミリーに属する) 型です。
2. ビットフィールドのスペースは、必要に応じて 4 バイト ( int ) または 1 バイト ( char ) の 形式で解放されます。
3. ビット セグメントには不確実な要素が多く、ビット セグメントはクロスプラットフォームではないため、移植性を重視するプログラムではビット セグメントの使用を避けてください。
では、メモリ内ではどのように割り当てられるのでしょうか?
//一个例子
struct S
{
   char a:3;
   char b:4;
   char c:5;
   char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?

 解析:

アイデア: まず、バイトがいっぱいであるか不十分な場合は、別のバイト領域を作成します。

 ここで、以前のビッグ エンディアンとスモール エンディアンのバイト オーダーと区別する必要があり、今回は移動単位がビット、後者の単位がバイトになります。

2.17 ビットセグメントにおけるクロスプラットフォームの問題

1. intビットフィールドが 符号付き数値 とみなされるか、符号なし数値とみなされるかは不明です。
2. ビットフィールドの 最大ビット数は 決定できません。( 16 ビット マシンの場合 は最大16、32ビット マシンの場合 は最大 32、27と記述されます。16ビットマシンでは問題が発生します。
3. ビット セグメントのメンバーがメモリ内で左から右に割り当てられるか、右から左に割り当てられるかはまだ定義されていません。
4. 構造に 2 つのビット セグメントが含まれており、2 番目のビット セグメントが大きすぎて最初のビット セグメントの残りのビットを収容できない場合、残りのビットを破棄するか利用するかがわかりません。
特にネットワーク プロトコルで使用されるもので、興味のある学生はそれについて学ぶことができます。
要約:
     構造と比較すると、ビットセグメントは同じ効果を達成でき、スペースを大幅に節約できますが、クロスプラットフォームの問題があります。

2.2 列挙

     ある状況で考えられる結果をまとめて列挙し、使用する際にはその状況に適したデータをそのまま使用することです。

好き:

12 か月を 1 つのリストにすることができます

性別も1つずつ記載可能

次のように使用します。

enum Color//颜色
{
   RED=1,
   GREEN=2,
   BLUE=4
};
{} 内の内容は、列挙型の取り得る値であり、列挙定数 とも呼ばれます。
    初期化しない場合はデフォルトで 0から始まり 1ずつ 増加しますが 、定義時に初期値を代入することももちろん可能です。(注: #define 変更定数と同様、データは定数領域に配置され、一度定義すると変更することはできません。)
好き:
enum  a
{
   c,   // 默认值为 0
   b,   //                1
   k = 200, //赋值为200
   z,        //  由前+ 1,为 201
   t          //  同理        202
};

2. 21 列挙の利点

  • なぜ列挙型を使用するのでしょうか?
#define を使用し て定数を定義    できますが、なぜ列挙型を使用するのでしょうか?
列挙型の利点:
  • 1.コードの可読性と保守性を向上させます (#define を減らします)。
  • 2. #defineで定義された識別子と比較して、列挙型にはより厳密な型チェックが行われます
(C++ では、列挙定数が割り当てられると、右側のデータが列挙され、列挙メンバーであるかどうかが判断されます)
  • 3.命名汚染を防止します (名前の変更を避けるために、範囲を列挙名によって制限できます)
  • 4.デバッグが簡単 (プリコンパイル中に置き換えられる #define 定義とは異なり、デバッグして対応する値を見つけることができますが、ソースを追跡するのは簡単ではありません)
  • 5.使いやすく、一度に複数の定数を定義可能

2.3 コンソーシアム

共用体型の定義
    Union も特別なカスタム型であり、この型で定義される変数には一連のメンバーが含まれており、これらのメンバーが 同じ空間を共有することが特徴です (したがって、union は Union とも呼ばれます)。例えば:
#include<stdio.h>
union p1
{
    char i;
    int z;
}a;
int main()
{
    printf("%p\n", &(a.i));
    printf("%p\n", &(a.z));
    return 0;
}

 

 結果:

関節の特徴
    共用体のメンバーは同じメモリ空間を共有するため、そのようなジョイント変数のサイズは少なくとも最大のメンバーのサイズになります(
Union は少なくとも最大のメンバーを保存できなければなりません)。
  • 共用体のメモリ サイズも、構造メモリのアライメント原則に従う必要があります。
    インタビューの質問: ジョイントの特性を利用してマシンのビッグ エンディアンとリトル エンディアンを判断する方法を想像してみてください。
#include<stdio.h>
union p1
{
	char i;
	int z;
}a;
int main()
{
	a.i = 5;
	a.z = 1;
    a.i == 1 ? printf("小端") : printf("大端");
	return 0;
}

結果はリトルエンディアンで、char i を使用するとデータの最初のバイトしか読み取れず、int z で変更された値が最初のバイトに存在すると判断できるため、マシンはリトルエンディアンになります。

3. 結論

    このセクションはここで終了です。閲覧していただいた友人に感謝します。ご提案があれば、コメント欄にコメントしてください。

 

おすすめ

転載: blog.csdn.net/qq_72112924/article/details/130005582
おすすめ