C++のメモリ位置合わせの説明

8110 ワード

ネット上には、バイトの位置合わせやデータの位置合わせ、メモリの位置合わせを紹介する文章がたくさんあります.名前は違いますが、紹介の内容はほぼ同じです.ここではメモリの位置合わせを行います.注意:以下の内容は主にネットワークから来ています.
メモリアライメントは、通常、データアライメントとも呼ばれ、コンピュータがデータ型の合法的なアドレスにいくつかの制限を加え、あるタイプのオブジェクトのアドレスがある値K(通常、2、4、8、16、32または64)の倍数でなければならないことを要求する.
現代のコンピュータではメモリ空間はbyteによって区分されており、理論的には任意のタイプの変数へのアクセスは任意のアドレスから開始できるようだが、実際には特定のタイプの変数にアクセスする際に特定のタイプのメモリアドレスにアクセスすることが多いため、順序の排出ではなく、様々なタイプのデータが一定の規則に従って空間的に配列される必要がある.これが位置合わせです.
メモリ位置合わせの理由:
(1). プラットフォーム原因(移植原因):すべてのハードウェアプラットフォームが任意のアドレス上の任意のデータにアクセスできるわけではない.一部のハードウェアプラットフォームでは、特定のタイプのデータしか取得できません.そうしないと、ハードウェア例外が放出されます.
(2). パフォーマンスの理由:データ構造(特にスタック)は、可能な限り自然境界に位置合わせする必要があります.なぜなら、プロセッサは、位置合わせされていないメモリにアクセスするために2回のメモリアクセスを行う必要があるからです.一方、整列したメモリアクセスには1回のアクセスしか必要ありません.
位置合わせ値は、1、2、4、8、16などの2乗の次数でなければなりません.変数がnバイトで整列されている場合、変数の開始アドレスはnの倍数でなければなりません.
各特定のプラットフォーム上のコンパイラには独自のデフォルトの「整列係数」があり、#pragma pack(n)を設定することで、すべての整列がnの整数倍に整列していることをコンパイラに伝えることができます.
構造体では、構造全体のサイズが最大フィールドサイズの整数倍でなければなりません.
プロセッサがメモリ内のデータをすばやく読み書きできるように、デフォルトでは、コンパイラは次のようにします.
(1). 1バイトの変数、例えばcharタイプの変数は、任意のアドレスの位置に置かれる.
(2). 2バイトの変数、例えばshortタイプの変数は、2の整数倍のアドレスに置かれる.
(3). 4バイトの変数、例えばlong/floatタイプの変数は、4の整数倍アドレスに置かれる.
(4). 8バイトの変数、例えばlong long/uint 64_tまたはdoubleタイプの変数は、8の整数倍アドレスに配置されます.
(5). 16バイトの変数は、デフォルトの位置合わせが8であるため、8の整数倍アドレスに配置されます.
変数のメモリ内の順序は、変数を定義する順序と同じです.整列方式を満たすために、変数間にパディングバイトを加え、後続の変数を整列方式のルールに従ったアドレスに置く.
strcut/class/unionメモリ整列規則:
1.pragma packマクロの整列規則がない:
(1). 構造体の初期記憶位置は、構造体の中で最大のデータ型によって除去される必要がある.
(2). 各データ・メンバーの格納の開始位置は、自身のサイズの整数倍である(例えば、intが32ビットマシンで4バイトである場合、int型メンバーは4の整数倍アドレスから格納される).
(3). 構造体の合計サイズ(すなわちsizeofの結果)は、構造体メンバーの最大の整列モジュール数の整数倍である必要があります.満たされない場合は、必要に応じて空きバイトが自動的に入力されます.
(4). 構造体が別の構造体メンバーを含む場合、含まれる構造体メンバーは、元の構造体内部の最大整列モジュール数の整数倍アドレスから格納される(例えばstruct aにはstruct bが格納され、bにはchar、int、doubleなどの要素が格納され、bは8の整数倍から格納されるべきである).
(5). 構造体にはchar a[3]のような配列メンバーが含まれており、その位置合わせは3つのcharをそれぞれ書くのと同じであり、つまり1バイトで位置合わせされている.書き込み:typedef char Array[3]の場合、Arrayのようなタイプの位置合わせは、長さ3ではなく1バイトで位置合わせされます.
(6). 構造体に共通体メンバーが含まれている場合、その共通体メンバーは、元の共通体内部の最大整列モジュール数の整数倍アドレスから格納されます.
2.pragma packマクロの位置合わせが存在する:
(1). #pragma pack(n)/コンパイラはnバイトで整列します
(2). #pragma pack()/カスタムバイト整列解除
C++11のalignas関数でタイプ、オブジェクト、または変数をどのくらいのバイトで整列させるかを指定できます.alignof関数でタイプ、オブジェクト、または変数がどのくらいのバイトで整列されているかを判断できます.
次は、他の記事のcopyのテストコードです.詳細は、対応するreferenceを参照してください.
memory_alignment.cppの内容は以下の通りです.
#include "memory_alignment.hpp"
#include 
#include 

//#pragma pack(1) // use #pragma pack set memory alignment

namespace memory_alignment_ {

//////////////////////////////////////////////////////
int test_memory_alignment_1()
{

{ // struct	
	typedef struct A {char c;} A;
	typedef struct B {int i;} B;
	typedef struct C {double d;} C;
	typedef struct D {char c; int i;} D;
	typedef struct E {char* p;} E; // 32bits is 4, 64bits is 8
	typedef struct F {char* p; int* p2;} F;
	typedef struct G {char c1; char c2; char c3;} G;
	typedef struct H {char c; int* p;} H;
	typedef struct I {char c; int* p; int i;} I;
	typedef struct J {char c; int i; int* p;} J;
	typedef struct K {} K; // C++ size is 1, but C is 0
	fprintf(stdout, "size: A: %d, B: %d, C: %d, D: %d, E: %d, F: %d, G: %d, H: %d, I: %d, J: %d, K: %d
", sizeof(A), sizeof(B), sizeof(C), sizeof(D), sizeof(E), sizeof(F), sizeof(G), sizeof(H), sizeof(I), sizeof(J), sizeof(K)); fprintf(stdout, "size: short: %d, long: %d, float: %d, long long: %d, double: %d, uint64_t: %d
", sizeof(short), sizeof(long), sizeof(float), sizeof(long long), sizeof(double), sizeof(uint64_t)); } return 0; } ////////////////////////////////////////////////////////// // reference: https://stackoverflow.com/questions/17091382/memory-alignment-how-to-use-alignof-alignas int test_memory_alignment_2() { { // alignas: // Alignment of 16 means that memory addresses that are a multiple of 16 are the only valid addresses. alignas(16) int a[4]; alignas(1024) int b[4]; fprintf(stdout, "address: %p
", a); fprintf(stdout, "address: %p
", b); // alignof: , size_t if (alignof(a) != 16 || (unsigned long long)a % 16 != 0) { fprintf(stderr, "a must be 16 byte aligned.
"); return -1; } if (alignof(b) != 1024 || (unsigned long long)b % 1024 != 0) { fprintf(stderr, "b must be 1024 byte aligned.
"); return -1; } } { // every object of type sse_t will be aligned to 16-byte boundary struct alignas(16) sse_t { float sse_data[4]; }; // the array "cacheline" will be aligned to 128-byte boundary alignas(128) char cacheline[128]; } return 0; } ////////////////////////////////////////////////////////////// // reference: https://en.cppreference.com/w/cpp/language/alignof struct Foo { int i; float f; char c; }; struct Empty {}; struct alignas(64) Empty64 {}; int test_memory_alignment_3() { std::cout << "Alignment of" "
" "- char : " << alignof(char) << "
" "- pointer : " << alignof(int*) << "
" "- class Foo : " << alignof(Foo) << "
" "- empty class : " << alignof(Empty) << "
" "- alignas(64) Empty: " << alignof(Empty64) << "
"; return 0; } ////////////////////////////////////////////////////////////////////// // reference: https://msdn.microsoft.com/en-us/library/dn956973.aspx int test_memory_alignment_4() { struct x_ { char a; // 1 byte int b; // 4 bytes short c; // 2 bytes char d; // 1 byte } MyStruct; // The compiler pads this structure to enforce alignment naturally. // The following code example shows how the compiler places the padded structure in memory:Copy // Shows the actual memory layout /*struct x_ { char a; // 1 byte char _pad0[3]; // padding to put 'b' on 4-byte boundary int b; // 4 bytes short c; // 2 bytes char d; // 1 byte char _pad1[1]; // padding to make sizeof(x_) multiple of 4 } MyStruct; */ // 1. Both declarations return sizeof(struct x_) as 12 bytes. // 2. The second declaration includes two padding elements: // 3. char _pad0[3] to align the int b member on a four-byte boundary // 4. char _pad1[1] to align the array elements of the structure struct _x bar[3]; // 5. The padding aligns the elements of bar[3] in a way that allows natural access. return 0; } } // namespace memory_alignment_

CMakeLists.txtの内容は以下の通りです.
PROJECT(CppBaseTest)
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)

#   C++11
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -Wall -O2 -std=c11")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}  -g -Wall -O2 -std=c++11")
#   C++14, when gcc version > 5.1, use -std=c++14 instead of c++1y
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}  -g -Wall -O2 -std=c++1y")

MESSAGE(STATUS "project source dir: ${PROJECT_SOURCE_DIR}")
SET(PATH_SRC_FILES ${PROJECT_SOURCE_DIR}/./../../demo/CppBaseTest)
MESSAGE(STATUS "path src files: ${PATH_SRC_FILES}")

#           
INCLUDE_DIRECTORIES(${PATH_SRC_FILES})

#            :*.cpp
FILE(GLOB_RECURSE CPP_LIST ${PATH_SRC_FILES}/*.cpp)
FILE(GLOB_RECURSE C_LIST ${PATH_SRC_FILES}/*.c)
#MESSAGE(STATUS "cpp list: ${C_LIST}")

#        
ADD_EXECUTABLE(CppBaseTest ${CPP_LIST} ${C_LIST})
#    target          ,          ,         
TARGET_LINK_LIBRARIES(CppBaseTest pthread)

build.shスクリプトの内容は以下の通りです.
#! /bin/bash

real_path=$(realpath $0)
dir_name=`dirname "${real_path}"`
echo "real_path: ${real_path}, dir_name: ${dir_name}"

new_dir_name=${dir_name}/build
mkdir -p ${new_dir_name}
cd ${new_dir_name}
cmake ..
make

cd -

コンパイルおよびテスト方法は、まずbuildを実行する.shを実行してから./build/CappBaseTestでいいです.
GitHub: https://github.com/fengbingchun/Messy_Test