メインコンテンツ

不必要なパディング

struct のメンバーを再配置してアライメント要件を満たすことでメモリを節約できる場合に、この要件を満たすためのメンバーがパディングされる

R2021b 以降

説明

このチェッカーは、アライメント要件を満たすために struct オブジェクトのメンバーの配置で追加のパディングが必要になる場合、このオブジェクトにフラグを設定します。このような struct オブジェクトのメンバーを再配置することで、追加のパディングを必要とせずにアライメント要件を満たせる場合があります。アライメントのためのパディングが不要になるため、パディングを排除してメモリを節約できます。64 ビット システムの次の struct について考えます。

 struct A {
      uint32_t m1;// 4 bytes
      uint64_t m2;// 8 bytes
      uint32_t m3;// 4 bytes
  }; 
 
C/C++ では、速度を最大化するために、可能な場合は 1 サイクルで変数を読み取ることを要件としています。このシステムでは、1 サイクルで 8 バイトを読み取ることができます。m1m2 が連続して配置される場合、マシンが m2 を読み取るためには 2 サイクルが必要になります。代わりに、変数 m1 を 4 バイトでパディングしてから 8 バイトのスロットに単独で配置します。続いて m2 を単独で 8 バイトのスロットに配置します。struct A のアライメント要件を満たすために、変数 m3 もパディングされます。m1m2m3 のサイズを合計すると 16 バイトですが、パディングが行われているため、A のサイズは 24 バイトになります。

Polyspace® は、struct のメンバーを再配置することでアライメント要件を満たせる場合に、この欠陥を報告します。たとえば、A のメンバーを再配置すれば、パディングを排除できます。

 struct A {
      uint64_t m2;// 8 bytes
      uint32_t m1;// 4 bytes
      uint32_t m3;// 4 bytes
  }; 
 
ここでは、最初に m2 を 8 バイトのスロットに配置しています。次に、m1m3 を別の 8 バイトのスロットに一緒に配置します。この再配置により、パディングを排除できます。

リスク

不必要なパディングはメモリを浪費します。この浪費によって、次のようないくつかの悪影響が生じることがあります。

  • 必要以上のメモリを使用すると、利用可能なメモリが使い果たされて、ページングの障害につながる可能性があります。

  • memcpymemcmp などの関数の処理時間が長くなる可能性があります。

修正方法

この欠陥を修正するには、不必要なパディングを排除するために struct のメンバーを再配置します。最初に最大の struct のメンバーを宣言してから、同じサイズのメンバーの宣言を 1 つにまとめます。また、pragma 命令を使用してパディングを排除することもできます。

パフォーマンスの改善の程度は、使用しているコンパイラ、ライブラリ実装、環境によって異なる可能性があります。

すべて展開する

#include <stdint.h>
struct GlobalStructA {  
    uint32_t m1;                
    uint64_t m2;
    uint32_t m3;                
};

// Using pragma pack to eliminate padding
//
#pragma pack(push, 1)
struct GlobalStructPragmaPack {          
    uint8_t  m1;
    uint64_t m2;
    uint8_t  m3;
};
#pragma pack(pop)
struct Array                   
{
    uint8_t  m1[5];
    uint64_t m2;
    uint8_t  m3[3];
};
struct StructWithBitField    
{
    uint8_t  m1 : 1;
    uint8_t     : 1;
    uint8_t     : 1;
    uint8_t  m2 : 1;

    uint64_t m3;

    uint8_t  m4 : 1;
    uint8_t     : 1;
    uint8_t     : 1;
    uint8_t  m5 : 1;
};

この例では、Polyspace が不必要なパディングを含む C スタイルの struct オブジェクトにフラグを設定します。Polyspace は、プロセッサの語長は既定で 32 ビットであると仮定します。オプション -target x86_64 を使用して、この例を実行します。

  • GlobalStructA オブジェクト内で最初に 32 ビット変数 m1 が宣言されているため、この変数は 64 ビットになるまでパディングされます。このパディングは、m2 の後に m1 を宣言することによって排除できることから、Polyspace はこのオブジェクトにフラグを設定します。

  • オブジェクト GlobalStructPragmaPack にも、そのメンバー宣言の順序に同様の問題がありますが、pragma 命令によってパディングは排除されます。Polyspace は、このオブジェクトにはフラグを設定しません。

  • Array オブジェクトでは、m1[5]m2 の前に宣言されているため、40 ビットの配列が 64 ビットになるまでパディングされます。24 ビットの配列 m3 は 64 ビットになるまでパディングされます。これらのパディングは、最初に m2 を宣言することによって排除できます。Array には不必要なパディングが含まれるため、Polyspace はこのオブジェクトにフラグを設定します。

  • オブジェクト StructWithBitField には、それぞれ 4 バイトからなる 2 つのビットフィールがあります。これら 2 つのビットフィールドの間で m3 が宣言されているため、これらのビットフィールドはパディングされます。Polyspace は、このオブジェクトにフラグを設定します。

修正

この欠陥を修正するには、宣言の順序を変更してパディングを排除します。次に例を示します。

  

#include <stdint.h>
struct GlobalStructA {          
	uint64_t m2;
	uint32_t m1;                
	uint32_t m3;                
};

// Using pragma pack to eliminate padding
//
#pragma pack(push, 1)
struct GlobalStructPragmaPack {           
	uint64_t m2;
	uint8_t  m1;
	uint8_t  m3;
};
#pragma pack(pop)
struct Array                   
{
	uint64_t m2;
	uint8_t  m1[5];
	uint8_t  m3[3];
};
struct StructWithBitField    
{
	uint64_t m3;
	
	uint8_t  m1 : 1;
	uint8_t     : 1;
	uint8_t     : 1;
	uint8_t  m2 : 1;

	uint8_t  m4 : 1;
	uint8_t     : 1;
	uint8_t     : 1;
	uint8_t  m5 : 1;
};
#include <string>
#include<stdint.h>
class GlobalClassWithString {
    uint32_t    m1;
    std::string m2;
    uint32_t    m3;
};
class GlobalClassC {
  public:
    uint32_t m1;
  protected:
    uint64_t m2;
  private:
    uint32_t m3;
};

Polyspace は、プロセッサの語長は既定で 32 ビットであると仮定します。オプション -target x86_64 を使用して、この例を実行します。先行するクラスでのメンバー宣言の順序により、これらのクラスには不必要なパディングが含まれます。Polyspace は、これらのクラスにフラグを設定します。メンバーの publicprivate、または protected ラベルは、メモリ内でのメンバーの配置に影響を与えません。

修正

この欠陥を修正するには、宣言の順序を変更してパディングを排除します。次に例を示します。

#include <string>
#include<stdint.h>
class GlobalClassWithString{
    std::string m2;
	uint32_t    m1;
    uint32_t    m3;
};
class GlobalClassC {
  protected:
    uint64_t m2;  
  public:
    uint32_t m1;
  private:
    uint32_t m3;
};
#include <stdint.h>
struct SmallPadding {     //Defect in 16-bit, no defect in 32-bit
    uint8_t m1;
    uint8_t m2;
    uint8_t m3;
    uint8_t m4;
    uint8_t m5;
    uint8_t m6;
    uint8_t m7;
    uint32_t m8;
    uint8_t m9;
    uint8_t m10;
    uint8_t m11;
    uint8_t m12;
    uint8_t m13;
    uint8_t m14;
    uint8_t m15;
};

この例は、プロセッサの語長に対するこのチェッカーの感度を示しています。オブジェクト SmallPadding には、16 ビットのパディングが含まれています。これは、struct の合計サイズと比べると小さいものです。プロセッサの語長が大きい場合、サイズの小さいパディングによって非効率になることはあまりありません。たとえば、プロセッサのアライメントが 64 ビットの場合、SmallPadding を読み取るためには、パディングにかかわらず 3 サイクルが必要です。この場合、Polyspace はオブジェクトにはフラグを設定しません。プロセッサの語長が小さい場合は、パディングによって非効率になる可能性があります。たとえばプロセッサのアライメントが 16 ビットの場合、パディングの追加によってオブジェクトの読み取りが非効率になる可能性があります。この場合、Polyspace は SmallPadding にフラグを設定します。Polyspace 解析でプロセッサのアライメントを 16 ビットに設定するには、オプション -target mcpu -align 16 を使用します。汎用ターゲット オプションを参照してください。

結果情報

グループ: パフォーマンス
言語: C | C++
既定値: オフ
コマンド ライン構文: UNNECESSARY_STRUCT_PADDING
影響度: Medium

バージョン履歴

R2021b で導入