ARM アーキテクチャと C 言語 (Wei Dongshan) の学習メモ (7) - 共用体、メモリ アライメントの問題、ビット フィールド、ヘッダー ファイル


1. 構造体と共用体はどのようにしてメモリに格納されますか?

(1) 構造

構造体のメモリへの格納方法は配列と同様で、連続したメモリ空間であり、各メンバ変数は一定のバイト サイズを占有します

構造体が定義されると、コンパイラはメンバー変数の型と順序に従って構造体のメモリ レイアウトを決定します。通常、メンバ変数は定義順に順番にメモリ上に格納され、各メンバ変数の先頭アドレスはメンバ変数のサイズの整数倍になります。構造体のメンバー変数に異なるデータ型が含まれている場合、コンパイラは、構造体のアクセス効率と信頼性を確保するために、データ型のアライメント要件に従って調整を行うことがあります。

たとえば、次の構造体定義について考えてみましょう。

struct person {
    
    
    char name[20];
    int age;
    float height;
};

メモリ内の構造体の開始アドレスが 0x1000 で、char 型が 1 バイト、int 型が 4 バイト、float 型が 4 バイトを占めるとします。その後、構造体のメンバー変数は次の順序でメモリに保存されます。

0x1000: 名前[0]
0x1001: 名前[1]

0x1013: 名前[19]
0x1014: 年齢 (下位バイト) 0x1015
: 年齢
0x1016: 年齢 0x1017:
年齢 (上位バイト)
0x1018: 身長 (下位バイト)
0x1019:高さ0x101 A: 高さ
0x101 B : 高さ(上位バイト) 構造体のメンバ変数は定義順にメモリ上に格納されており、各メンバ変数の開始アドレスはそのサイズの整数倍であることがわかります。この格納方法により、構造のアクセス効率と信頼性を高めることができる。

(2) コンソーシアム

共用体のメンバも定義順に従って同じメモリ空間に順番に格納されます。違いは、共用体のすべてのメンバーが同じメモリ空間を共有するため、アクセスできるのは 1 つのメンバーのみで、他のメンバーの値は上書きされる可能性があることですしたがって、共用体を使用する場合は特別な注意が必要です。

たとえば、次の共用体の定義について考えてみましょう。

union data {
    
    
    int i;
    float f;
    char str[20];
};

メモリ内の共用体の開始アドレスが 0x1000 で、int 型が 4 バイト、float 型が 4 バイト、char 型が 1 バイトを占めるとします。その後、共用体のメンバー変数は次の順序でメモリに格納されます。

0x1000: i (下位バイト)
0x1001: i
0x1002: i
0x1003: i (上位バイト)
または
0x1000: f (下位バイト) 0x1001
: f
0x1002: f 0x1003
: f (上位バイト)
または
0x1000: str[0]
0x1001: str[1]

0x101 3: str[19]
共用体内のメンバ変数は定義順に同じメモリ空間に順次格納されており、各メンバ変数の開始アドレスはメンバ変数のサイズの整数倍であることがわかります。ただし、アクセスできるのはこれらのメンバーのうち 1 つだけであり、他のメンバーの値は上書きされる可能性があります。たとえば、先に共用体内の int 型のメンバ変数にアクセスし、次に共用体内の float 型のメンバ変数にアクセスすると、元々 int 型のメンバ変数に格納されていた値が上書きされる可能性がありますしたがって、共用体を使用するときは、エラーを避けるために特別な注意を払う必要があります。

(3) メモリ上の二人の占有空間

このコードを例として取り上げます。

#include <stdio.h>

struct example_struct {
    
    //结构体
    int a;
    char b;
    double c;
};

union example_union {
    
    //联合体
    int a;
    char b;
    double c;
};

int main() {
    
    
    struct example_struct s;
    printf("Size of struct: %lu bytes\n", sizeof(s));

    union example_union u;
    printf("Size of union: %lu bytes\n", sizeof(u));
    return 0;
}

結果は次のとおりです。

Size of struct: 16 bytes
Size of union: 8 bytes


(1) 構造体については、example_struct に int 整数、char 文字型、および double 倍精度浮動小数点型が含まれており、メモリ内で 16 バイト (4 バイト + 4 バイト + 8 バイト) を占有していることがわかります
(2) 共用体 example_union の最大のメンバーは double 型であるため、メモリ内で 8 バイトを占有します。

つまり、構造体のメモリ サイズは、内部メンバ変数のサイズに従って、メモリ アライメントの原理に基づいて計算されます
共用体のメモリ サイズ。すべてのメンバーが同じメモリ領域を占有するため、そのメモリ領域は最大のメンバーのメモリ サイズになります

2. メモリ調整機構

1. 質問に導く

struct example_struct {
    
    //结构体
    int a;
    char b;
    double c;
};

 struct example_struct s;
 printf("Size of struct: %lu bytes\n", sizeof(s));

1 つの結果によると、構造体 s のサイズは 16 バイトです。つまり、int 整数、char 文字型、および double 倍精度浮動小数点型が含まれており、メモリ内で 16 バイト (4 バイト + 4 バイト + 8 バイト) を占有しますでは、なぜ char 自体は 1 バイトであるのに、ここでは 4 バイトのスペースを占めるのでしょうか?
ここに画像の説明を挿入
文字 b は最初の 1 バイトだけが占有されますが、最後の 3 バイトは空いてしまい、強制的に 4 バイトになります。

2. 説明する

メモリ アライメント メカニズムとは、メモリを割り当てて使用するときに、システムが特定のルールに従ってメモリを割り当ててアライメントすることにより、プログラムのパフォーマンスとセキュリティが向上することを意味します。

C 言語では、各変数は一定のバイト サイズを占有する必要があります。たとえば、int 型は通常 4 バイト、double 型は通常 8 バイトを占有します。メモリ アクセス効率を向上させるために、システムは特定のルールに従って整列されたアドレスに変数を格納します。これにより、メモリ アクセスの数が削減され、プログラムのパフォーマンスが向上します

メモリ アライメント メカニズムのルールは、通常、ハードウェア アーキテクチャとオペレーティング システムによって決まります。x86 アーキテクチャのコンピュータでは、通常、4 バイトまたは 8 バイトに従ってアライメントされます。たとえば、int 型変数は通常 4 バイトで整列されたアドレスに割り当てられ、double 型変数は通常 8 バイトで整列されたアドレスに割り当てられます。

C 言語では、#pragma Pack(n) ディレクティブを使用してデフォルトのアライメントを変更できます。ここで、n は指定されたアライメント値です。たとえば、#pragma Pack(1) ディレクティブを使用してアライメント値を 1 バイトに設定すると、メモリ アライメント メカニズムがキャンセルされます。ただし、メモリ アライメント メカニズムをキャンセルすると、プログラムのパフォーマンスと移植性に影響を与える可能性があるため、使用には注意が必要です。

ここに画像の説明を挿入

メモリアライメントを使用しない場合、長さが等しくないデータの場合、CPU が読み出す際に 2 つのセグメントを挟んで 2 回読み込む必要があり、データをつなぎ合わせる必要があり、CPU の動作効率に大きな影響を与えます。

3. ビットフィールド

(1) ビットフィールドとは

ビット フィールドは C 言語のデータ型で、プログラマはこれを使用して、バイト全体またはワード全体ではなく、指定されたビット数の空間を占有する構造体メンバーまたは共用体メンバーを定義できます。通常、メモリを節約したり、ハードウェアと通信したりするために使用されます。

C 言語では、ビット フィールドはコロン (:) 演算子を使用して宣言できます。構文は次のとおりです。

struct {
    
    
    type [member_name] : width;
};

このうち、type はビットフィールドの基本データ型を示し、member_name はビットフィールドの名前 (オプション) を示し、width はビットフィールドが占めるビット数を示します。例えば:

struct {
    
    
    unsigned int flag: 1;
    unsigned int value: 15;
};

上記のコードは、フラグと値という 2 つのビット フィールド メンバーを含む構造体を定義します。このうち、フラグは 1 ビット、値は 15 ビットを占めます。

ビット フィールドを使用する場合は、次の点に注意する必要があります。
ビット フィールドの幅は、その基本データ型の幅を超えることはできません。
構造体のビット フィールドの順序とサイズは通常、コンパイラによって決定され、#pragma Pack ディレクティブを使用してアライメントを制御できます。
ビット フィールドの動作はコンパイラによって異なる場合があるため、注意して使用してください。
ヒント: 記事の概要は次のとおりです:
例: 上記は、今日お話しする内容です。この記事では、パンダの使用法を簡単に紹介するだけであり、パンダは、データを迅速かつ簡単に処理できるようにする多数の関数とメソッドを提供します。

(2) ビットフィールドの適用

ビット フィールドは、特に大量の bool 型または列挙型データを保存する必要がある場合に、メモリ領域を節約するために使用できます。ビットフィールド アプリケーションの例をいくつか示します。

1. ブール値データを保存する

ブール型には true と false の 2 つの値のみがあります。ビット フィールドを使用すると、複数のブール データを 1 バイトに圧縮できるため、メモリ スペースを節約できます。

struct bool_fields {
    
    
    unsigned int a: 1;
    unsigned int b: 1;
    unsigned int c: 1;
    unsigned int d: 1;
};

この例では、4 つの bool 型メンバーを含む構造体 bool_fields を定義します。各メンバーは 1 ビットのみを占有し、1 バイトに圧縮できます。
この構造体のサイズは 4 バイトですが、変数 a/b/c/d は最下位バイトの下位 4 ビットのみを占めます。

2. 列挙型のデータを格納する

通常、列挙型には少数の値しかなく、ビット フィールドを使用すると、複数の列挙型のデータを 1 バイトに圧縮できるため、メモリ領域を節約できます。

enum color {
    
    RED, GREEN, BLUE};

struct color_fields {
    
    
    enum color a: 2;
    enum color b: 2;
    enum color c: 2;
    enum color d: 2;
};

この例では、4 つの列挙型メンバーを含む構造体 color_fields を定義します。各メンバーは 2 ビットを取り、1 バイトにパックできます。

3. ビットマップデータを保存する

ビットマップは画像を保存するために使用されるデータ構造であり、画像内の各ピクセルを 2 進数として表します。ビットフィールドを使用すると、ビットマップ データをより小さなストレージ領域に圧縮できるため、ストレージと送信のオーバーヘッドが削減されます。

struct bitmap {
    
    
    unsigned int width: 10;
    unsigned int height: 10;
    unsigned char data[1024];
};

この例では、ビットマップの幅と高さを含むビットマップ構造を定義します。幅と高さのビット数はそれぞれ 10 ビットで、表現できる最大値は 1023 です。ビットマップ データは 1024 バイトの文字配列に格納されます。

4. ヘッダファイル

1. ヘッダファイルの概念

C 言語のヘッダー ファイルとは、他の C ファイルから参照および呼び出すことができる、事前定義された関数、変数、マクロ定義、型宣言などの情報が含まれるファイルを指しますヘッダー ファイルは通常、ソース ファイルに含まれており、#include ディレクティブを使用して現在のファイルに含めることができます。

一般的に使用される C 言語ヘッダー ファイルの一部を次に示します。

stdio.h: printf、scanf、puts、gets などの入出力の関数とマクロ定義を定義します。
stdlib.h: malloc、calloc、realloc、exit、rand、srand などのいくつかの一般的な関数と型を定義します。
string.h: strcpy、strcat、strlen、strcmp、memset、memcpy などのいくつかの文字列処理関数とマクロ定義を定義します。
math.h: sqrt、sin、cos、exp、PI などの数学的演算に関連する関数と定数を定義します。
time.h: time、clock、strftime、tm などの時間関連の関数と型を定義します。
ctype.h: isalpha、isdigital、isspace、to lower、toupper などのいくつかの文字処理関数とマクロ定義を定義します。

2.externキーワード

(1) externの役割

extern は C 言語のキーワードで、変数または関数が他のファイルで定義されており、現在のファイルで使用できることを宣言するために使用されます。

具体的には、別のファイルで定義されている変数や関数を、あるファイル内で使用する場合は、 extern キーワードを使用して宣言する必要があります。たとえば、main.c ファイルで変数 x を使用し、この変数が別のファイル func.c で定義されている場合、extern キーワードを使用して main.c ファイルで宣言する必要があります。

// main.c
#include <stdio.h>

extern int x; // 声明变量x是在其他文件中定义的

int main() {
    
    
    printf("%d\n", x); // 使用变量x
    return 0;
}
// func.c
int x = 10; // 定义变量x

void func() {
    
    
    // ...
}

上の例では、main.c ファイルで変数 x を使用し、 extern キーワードを使用してそれを宣言しましたこのようにして、コンパイラーは変数 x が他のファイルで定義されていることを認識し、リンク時にそれを実際の定義に関連付けることができます。

変数に加えて、 extern キーワードを使用して、関数が他のファイルで定義されていることを宣言することもできます。次に例を示します。

// main.c
#include <stdio.h>

extern void func(); // 声明函数func是在其他文件中定义的

int main() {
    
    
    func(); // 调用函数func
    return 0;
}
// func.c
void func() {
    
    
    // ...
}

実際のプログラミングでは、複数のファイル間で変数や関数の定義を共有するために、ヘッダー ファイルで extern キーワードがよく使用されます。

(2) .h ファイル内で変数を定義できますか?

回答: C 言語では、ヘッダー ファイル (.h ファイル) で変数を定義することは一般的に推奨されません。ヘッダー ファイルの内容が複数のソース ファイルにインクルードされるためです。ヘッダー ファイルで変数を定義すると、これらのソース ファイルすべてにこの変数の定義が含まれることになり、実行時にコンパイル エラーや予期しない結果が発生する可能性があります

通常、ヘッダー ファイルには関数宣言、マクロ定義、型定義などの情報のみが含まれます。つまり、ヘッダー ファイルには宣言のみが含まれ、定義は含まれません。

複数のソース ファイルで変数を共有する必要がある場合は、この変数を 1 つのソース ファイルで定義し、 extern キーワードを使用して他のソース ファイルで宣言できます。このようにして、コンパイラはすべての参照を同じ変数定義にリンクし、定義の重複や不一致の問題を回避します。

おすすめ

転載: blog.csdn.net/qq_53092944/article/details/131886369