メインコンテンツ

不適切にデリファレンスされたポインター

ポインターが範囲外でデリファレンスされる

説明

ポインターのデリファレンスに関するこのチェックでは、ポインターが NULL であるか、その境界外を指すかどうか、または動的に割り当てられた後に割り当てを解除されたメモリを指すかどうかが判別されます。チェックは、ポインターをデリファレンスしたときのみ発生し、ポインターにアドレスを割り当てたり、別のポインターに再割り当てしたり、ポインターを関数に渡したりしたときには発生しません。

チェック メッセージには、ポインターのポイント先のサイズ、ポインター オフセット、および割り当てられたバッファーがバイト単位で示されます。"ポイント先のサイズとオフセットの合計がバッファー サイズよりも大きい" 場合、ポインターは範囲外を指します。

  • ポイント先のサイズ:ポインターをデリファレンスする場合にアクセスするバイト数です。たとえば、int* 型ポインターをデリファレンスする場合、最も一般的なターゲットでアクセスするバイト数は 4 となります。

  • バッファー サイズ:ポインターにアドレスを割り当てると、そのポインターにメモリのブロックが割り当てられます。そのポインターを使用してそのブロック外のメモリにアクセスすることはできません。このブロックのサイズはバッファー サイズです。たとえば、char 変数のアドレスをポインターに代入する場合、割り当てられるバッファー サイズは最も一般的なターゲットで 1 バイトとなります。

    場合によっては、サイズが明確な値ではなく範囲になる可能性があります。たとえば、サイズの不明な入力を指定して malloc を使用し、動的にバッファーを作成すると、Polyspace® では、配列サイズは入力データ型で許容される全範囲の値を取り得ると仮定されます。

  • オフセット:ポインター演算を使用して、割り当てられたメモリ ブロック内でポインターを移動できます。ポインターの初期位置と現在の位置の差がオフセットです。

    場合によっては、オフセットが明確な値ではなく範囲になる可能性があります。たとえば、ループ内で配列にアクセスすると、オフセットはループの反復ごとに値を変更し、ループ全体を通じて値の範囲を取ります。

たとえば、ポインターが配列を指す場合、

  • バッファー サイズは配列サイズです。

  • オフセットは配列の先頭とポインターの現在の位置の差です。

Buffer size is size of allocated buffer. Offset is difference between initial pointer location and pointer location when dereferenced.

このチェックでは [ターゲット プロセッサ タイプ] (-target) の指定を使用して、データ型のサイズを判別します。

すべて展開する

int main() {
    short x=0; 
    int *ptr = (int *) &x;
    *ptr = 2; 
    return 0;
}

この例では、変数 x のデータ型は short です。このデータ型は、ほとんどのターゲットで 2 バイトのバッファー サイズに相当します。

ただし、このバッファーのアドレスは int* 型ポインター ptr にキャストされます。int* 型ポインターは、ほとんどのターゲットでサイズが 4 バイトのバッファーを指します。ptr のデリファレンスには、これらの 4 バイトへのアクセスが必要になります。元の割り当ては 2 バイトのみであったため、ポインターのデリファレンスにより、許容範囲外のアクセスとなります。したがって、[不適切にデリファレンスされたポインター] チェックでレッド エラーが示されます。

[結果の詳細]:チェック メッセージで、ポインターが 2 バイトのバッファー内でオフセット 0 にある 4 バイトを指しているため、範囲外であることが確認されます。

  • "ポイント先のサイズ" は 4 バイトです。これは、ポインター ptrint を指しているためです。

  • "バッファー サイズ" は 2 バイトです。これは、バッファー サイズは指されている変数である x の型に基づくためです。x の型は short です。

  • "オフセット" は 0 です。これは、ポインターがバッファーの先頭を指しているためです。ポインター演算は関与しません。

データ型のサイズはターゲットによって異なることに注意してください。この例でのサイズは、最も一般的なターゲットの場合を表しています。

#define Size 1024

int input(void);

void main() {
    int arr[Size];
    int *p = arr;

    for (int index = 0; index < Size ; index++, p++){
        *p = input();
    }
    *p = input();
}

この例では以下のようになります。

  • for ループの前では、p は配列 arr の先頭を指します。

  • for ループの後では、p は配列の外部を指します。

for ループ後の p のデリファレンスに対し、[不適切にデリファレンスされたポインター] チェックでレッド エラーが示されます。

[結果の詳細]:チェック メッセージで、ポインターが 4096 バイトのバッファー内でオフセット 4096 にある 4 バイトを指しているため、範囲外であることが通知されます。

  • "ポイント先のサイズ" は 4 バイトです。これは、ポインター pint を指しているためです。

  • "バッファー サイズ" は 4096 バイトです。これは、バッファー サイズは配列のサイズに個々の配列要素型 (int) のサイズを乗算した値 (1024 * 4 バイト) であるためです。

  • "オフセット" は 4096 です。これは、演算 p++ により、ポインターが配列を移動してバッファーの先頭から 1024*4 バイト離れたところを指しているためです。

データ型のサイズはターゲットによって異なることに注意してください。この例でのサイズは、最も一般的なターゲットの場合を表しています。

修正 — 不適切なデリファレンスを削除

1 つの修正方法として、for ループ後の p の不適切なデリファレンスの削除があります。

#define Size 1024

int input(void);

void main() {
    int arr[Size];
    int *p = arr;
 	
    for (int index = 0; index < Size ; index++, p++) {
        *p = input();
    }
}    
typedef struct S {
    int f1;
    int f2;
    int f3;
} S;

void Initialize(int *ptr) {
    *ptr = 0;
    *(ptr+1) = 0;
    *(ptr+2) = 0;
}

void main(void) {
    S myStruct;
    Initialize(&myStruct.f1);
}

この例では、Initialize の本文で、ptr は構造体の第 1 のフィールドを指す int ポインターです。ptr を通して 2 番目のフィールドへのアクセスを試みると、[不適切にデリファレンスされたポインター] チェックでレッド エラーが示されます。

[結果の詳細]:チェック メッセージで、ポインターが 4 バイトのバッファー内でオフセット 4 にある 4 バイトを指しているため、範囲外であることが通知されます。

  • "ポイント先のサイズ" は 4 バイトです。これは、ポインター ptrint を指しているためです。

  • "バッファー サイズ" は 4 バイトです。これは、バッファー サイズは指されている変数である myStruct.f1 の型に基づくためです。myStruct.f1 の型は int です。

  • "オフセット" は 4 です。これは、演算 (ptr + 1) により、ポインターがバッファーの先頭から int の 1 サイズ分移動されるためです。

データ型のサイズはターゲットによって異なることに注意してください。この例でのサイズは、最も一般的なターゲットの場合を表しています。

修正 — 構造体フィールド外部へのメモリ アクセスを回避

可能な修正の 1 つは、構造体全体へのポインターを Initialize に渡すことです。

typedef struct S {
    int f1;
    int f2;
    int f3;
} S;

void Initialize(S* ptr) {
    ptr->f1 = 0;
    ptr->f2 = 0;
    ptr->f3 = 0;
}

void main(void) {
    S myStruct;
    Initialize(&myStruct);
}
#include<stdlib.h>

void main() {
    int *ptr=NULL;
    *ptr=0;
}

この例では、ptr に値 NULL が割り当てられます。したがって、ptr をデリファレンスすると、[不適切にデリファレンスされたポインター] チェックでレッド エラーが示されます。

ポインターが 0x0000 などの絶対アドレスで初期化される場合は、同様のエラーが表示されます。

#define RAM_START 0x0000

void main() {
    int *ptr;
    ptr = RAM_START;
    *ptr = 0;
}

[結果の詳細]:チェック メッセージで、ポインターが null であることが通知されます。ポインターが null であるため、チェックは、ポインターが割り当てられたバッファー内を指しているかどうかの検証に進みません。

修正 — NULL ポインターのデリファレンスを回避

1 つの修正方法として、NULL の代わりに変数のアドレスを使った ptr の初期化があります。

void main() {
    int var;
    int *ptr=&var;
    *ptr=0;
}

エラーを回避するには、解析目的のためだけに 0x0000 を別のアドレスで置き換えます。次に例を示します。

#ifdef POLYSPACE
#define RAM_START 0x0001
#else
#define RAM_START 0x0000
#endif

void main() {
    int *ptr;
    ptr = (int*)RAM_START;//Cast int to int*
    *ptr = 0;
}
解析オプション -D POLYSPACE を使用して、Polyspace 解析のためにアドレス 0x0000 が代替アドレス (この例では 0x0001) に置き換えられるようにします。プリプロセッサ定義 (-D) も参照してください。この解決策は、RAM_START が有効なアドレスであることがわかっている場合にのみ使用してください。

int getOffset(void);

void main() {
    int *ptr = (int*) 0 + getOffset();
    if(ptr != (int*)0)
        *ptr = 0;
}

この例では、オフセットが (int*) 0 に追加されますが、Polyspace ではその結果が有効なアドレスとして扱われません。したがって、ptr をデリファレンスすると、[不適切にデリファレンスされたポインター] チェックでレッド エラーが示されます。

[結果の詳細]:チェック メッセージで、ポインター自体は null ではないものの、メモリが割り当てられていない可能性があることが通知されます。

int arr[10];

int main(int arg, char* argv[]) {
    int *ptr = &arr[10];
    int val_ptr = *ptr;
    return 0;
}

この例では、ポインター ptr に、配列 arr に割り当てられているメモリを超えたアドレスが代入されています。ただし、この代入では [不適切にデリファレンスされたポインター] チェックはトリガーされません。このチェックは、ポインターがデリファレンスされた場合にのみ行われ、明確なエラー (レッド) を示します。

struct flagCollection {
    unsigned int flag1: 1;
    unsigned int flag2: 1;
    unsigned int flag3: 1;
    unsigned int flag4: 1;
    unsigned int flag5: 1;
    unsigned int flag6: 1;
    unsigned int flag7: 1;
};

char getFlag(void);

int main()
{
    unsigned char myFlag = getFlag();
    struct flagCollection* myFlagCollection;
    myFlagCollection = (struct flagCollection *) &myFlag;
    if (myFlagCollection->flag1 == 1)
        return 1;
    return 0;
}

この例では以下のようになります。

  • flagCollection のフィールドはタイプが unsigned int になります。したがって、フィールドそのものの占有は 7 ビットですが、flagCollection 構造体は 32 ビット アーキテクチャ内に 32 ビットのメモリを必要とします。

  • char アドレス &myFlagflagCollection ポインター myFlagCollection にキャストする場合、ポインターには 8 ビットのメモリしか割り当てられません。したがって、myFlagCollection のデリファレンスに対し、[不適切にデリファレンスされたポインター] チェックでレッド エラーが示されます。

[結果の詳細]:チェック メッセージで、ポインターが 1 バイトのバッファー内でオフセット 0 にある 4 バイトを指しているため、範囲外であることが通知されます。

  • "ポイント先のサイズ" は 4 バイトです。これは、ポインターが実質的に 1 つの unsigned int フィールドをもつ構造体を指しているためです。すべてのビット フィールドを 1 つの unsigned int 内に格納できます。

  • "バッファー サイズ" は 1 バイトです。これは、バッファー サイズは指されている変数である myFlag の型に基づくためです。この変数のデータ型は char です。

  • "オフセット" は 0 です。これは、ポインターがバッファーの先頭を指しているためです。ポインター演算は関与しません。

データ型のサイズはターゲットによって異なることに注意してください。この例でのサイズは、最も一般的なターゲットの場合を表しています。

修正 — 適切なビット フィールドタイプを使用

1 つの修正方法として、flagCollection のフィールド タイプとして unsigned int ではなく unsigned char を使用します。この例の場合は次のようになります。

  • 構造体 flagCollection は 8 ビットのメモリを必要とします。

  • char アドレス &myFlagflagCollection ポインター myFlagCollection にキャストする場合、ポインターにも 8 ビットのメモリが割り当てられます。したがって、myFlagCollection のデリファレンスに対し [不適切にデリファレンスされたポインター] チェックはグリーンになります。

struct flagCollection {
    unsigned char flag1: 1;
    unsigned char flag2: 1;
    unsigned char flag3: 1;
    unsigned char flag4: 1;
    unsigned char flag5: 1;
    unsigned char flag6: 1;
    unsigned char flag7: 1;
};

char getFlag(void);

int main()
{
    unsigned char myFlag = getFlag();
    struct flagCollection* myFlagCollection;
    myFlagCollection = (struct flagCollection *) &myFlag;
    if (myFlagCollection->flag1 == 1)
        return 1;
    return 0;
}
#include <stdlib.h>

void main(void)
{
    char *p = (char*)malloc(1);
    char *q = p;
    *q = 'a';
}

この例では、mallocNULLp に返す可能性があります。したがって、pq に割り当てて q をデリファレンスすると、[不適切にデリファレンスされたポインター] チェックでオレンジ エラーが示されます。

[結果の詳細]:チェック メッセージで、ポインターが null である可能性があることが通知されます。

修正 — malloc の戻り値を NULL についてチェック

1 つの修正方法として、q のデリファレンスの前に pNULL についてチェックします。

#include <stdlib.h>
void main(void)
{
    char *p = (char*)malloc(1);
    char *q = p;
    if(p!=NULL) *q = 'a';
}
#include <stdlib.h>

typedef struct {
   int state;
   union {
        char myChar;
        int myInt;
    } myVar;
} myType;

void main() {
    myType* myTypePtr;
    myTypePtr = (myType*)malloc(sizeof(int) + sizeof(char));
    if(myTypePtr != NULL) {
        myTypePtr->state = 0;
    }
}

この例では以下のようになります。

  • 共用体 myVarint 変数をフィールドとしてもつため、32 ビット アーキテクチャにおいて 4 バイトをこれに割り当てなければなりません。したがって、構造体 myType には 4+4 = 8 バイトを割り当てる必要があります。

  • malloc は、sizeof(int) + sizeof(char)=4+1=5 バイトのメモリを myTypePtr に返します。これは myType 構造体を指すポインターです。したがって、myTypePtr をデリファレンスすると、[不適切にデリファレンスされたポインター] チェックはレッド エラーを返します。

[結果の詳細]:チェック メッセージで、ポインターが 5 バイトのバッファー内でオフセット 0 にある 8 バイトを指しているため、範囲外であることが通知されます。

  • "ポイント先のサイズ" は 8 バイトです。これは、ポインター myTypePtrmyType 型を指しているためです。構造体 myType には次の 2 つのフィールドがあります。

    • int フィールド (4 バイト)。

    • union フィールド。共用体に含まれる最大の型は int (4 バイト) です。

  • "バッファー サイズ" は 5 バイトです。これは、ヒープに割り当てられたメモリのサイズが sizeof(int) + sizeof(char)、つまり 4+1 バイトであるためです。

  • "オフセット" は 0 です。これは、ポインターがバッファーの先頭を指しているためです。ポインター演算は関与しません。

データ型のサイズはターゲットによって異なることに注意してください。この例でのサイズは、最も一般的なターゲットの場合を表しています。

修正 — ポインターへの十分なメモリの割り当て

1 つの修正方法として、デリファレンスの前に 8 バイトのメモリを myTypePtr に割り当てるとします。

#include <stdlib.h>

typedef struct {
   int state;
   union {
        char myChar;
        int myInt;
    } myVar;
} myType;

void main() {
    myType* myTypePtr;
    myTypePtr = (myType*)malloc(sizeof(int) * 2);
    if(myTypePtr != NULL) {
        myTypePtr->state = 0;
    }
}
#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} rectangle;

typedef struct {
    int length;
    int breadth;
    int height;
} cuboid;

void main() {
    cuboid *cuboidPtr = (cuboid*)malloc(sizeof(rectangle));
    if(cuboidPtr!=NULL) {
        cuboidPtr->length = 10;
        cuboidPtr->breadth = 10;
    }
}

この例では、cuboidPtr はフィールドのうちの 2 つに十分対応するだけのメモリを獲得します。ANSI® C 標準ではこのような部分的なメモリ割り当ては許可されないため、cuboidPtr のデリファレンスに対し、[不適切にデリファレンスされたポインター] チェックでレッド エラーが示されます。

[結果の詳細]:チェック メッセージで、ポインターが 8 バイトのバッファー内でオフセット 0 にある 12 バイトを指しているため、範囲外であることが通知されます。

  • "ポイント先のサイズ" は 12 バイトです。これは、ポインター ptrcuboid 型を指しているためです。cuboid 型には 3 つの int メンバーがあるため、そのサイズは 3 * 4 バイトとなります。

  • "バッファー サイズ" は 8 バイトです。これは、ヒープに割り当てられたメモリのサイズは sizeof(rectangle) と等しくなるためです。rectange 型には 2 つの int メンバーがあるため、そのサイズは 2 * 4 バイトとなります。

  • "オフセット" は 0 です。これは、ポインターがバッファーの先頭を指しているためです。ポインター演算は関与しません。

データ型のサイズはターゲットによって異なることに注意してください。この例でのサイズは、最も一般的なターゲットの場合を表しています。

修正 — 完全なメモリの割り当て

ANSI C 規格を順守するには、cuboidPtr に完全なメモリを割り当てなければなりません。

#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} rectangle;

typedef struct {
    int length;
    int breadth;
    int height;
} cuboid;

void main() {
    cuboid *cuboidPtr = (cuboid*)malloc(sizeof(cuboid));
    if(cuboidPtr!=NULL) {
        cuboidPtr->length = 10;
        cuboidPtr->breadth = 10;
    }
}
修正 — Polyspace 解析オプションを使用

構造体に対して部分的にメモリを割り当て、しかも [不適切にデリファレンスされたポインター] のレッド エラーが発生しないようにできます。部分的なメモリの割り当てを可能にするには、[構成] ペインの [チェック動作] で、[構造体の不完全または部分的割り当てを許可する] を選択します。

#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} rectangle;

typedef struct {
    int length;
    int breadth;
    int height;
} cuboid;

void main() {
    cuboid *cuboidPtr = (cuboid*)malloc(sizeof(rectangle));
    if(cuboidPtr!=NULL) {
        cuboidPtr->length = 10;
        cuboidPtr->breadth = 10;
    }
}
#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} square;


void main() {
    square mySquare;
    char* squarePtr = (char*)&mySquare.length;
//Assign zero to mySquare.length byte by byte
    for(int byteIndex=1; byteIndex<=4; byteIndex++) {
        *squarePtr=0;
        squarePtr++;
    }
//Assign zero to first byte of mySquare.breadth
    *squarePtr=0;
}

この例では、squarePtrchar ポインターですが、これに整数 mySquare.length のアドレスが割り当てられています。その理由は次のとおりです。

  • char は 1 バイトを占めます。

  • int は 32 ビット アーキテクチャにおいて 4 バイトを占めます。

squarePtr はポインター演算を通して mySquare.length の 4 バイトにアクセスできます。ただし、別のフィールド mySquare.breadth の最初のバイトにアクセスすると、[不適切にデリファレンスされたポインター] チェックでレッド エラーが示されます。

[結果の詳細]:チェック メッセージで、ポインターが 4 バイトのバッファー内でオフセット 4 にある 1 バイトを指しているため、範囲外であることが確認されます。

  • "ポイント先のサイズ" は 1 バイトです。これは、ポインター squarePtrchar を指しているためです。

  • "バッファー サイズ" は 4 バイトです。これは、バッファー サイズは指されている変数である mySquare.length の型に基づくためです。mySquare.length の型は int です。

  • "オフセット" は 4 です。これは、演算 squarePtr++ を使用することで、バッファーの先頭から 1 つの int のサイズ分、ポインターが移動されているためです。

データ型のサイズはターゲットによって異なることに注意してください。この例でのサイズは、最も一般的なターゲットの場合を表しています。

修正 — フィールドではなく構造体のアドレスを割り当てる

1 つの修正方法として、mySquare.length ではなく、全構造体 mySquare のアドレスを squarePtr に割り当てるとします。これによって squarePtr は、ポインター演算を通して mySquare のすべてのバイトにアクセスできるようになります。

#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} square;


void main() {
    square mySquare;
    char* squarePtr = (char*)&mySquare;
//Assign zero to mySquare.length byte by byte
    for(int byteIndex=1; byteIndex<=4; byteIndex++) {
        *squarePtr=0;
        squarePtr++;
    }
//Assign zero to first byte of mySquare.breadth
    *squarePtr=0;
}
修正 — Polyspace 解析オプションを使用 (C++ では使用不可)

ポインターを使用して構造体の複数のフィールドに移動し、かつ [不適切にデリファレンスされたポインター] のレッド エラーが生じないようにできます。そのような移動を可能にするには、[構成] ペインの [チェック動作] で、[フィールド間のポインター演算を有効にする] を選択します。

このオプションは C++ プロジェクトでは使用できません。C++ では、多様型などの概念を扱う場合はポインター演算が非トリビアルになります。


#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} square;


void main() {
    square mySquare;
    char* squarePtr = (char*)&mySquare.length;
//Assign zero to mySquare.length byte by byte
    for(int byteIndex=1; byteIndex<=4; byteIndex++) {
        *squarePtr=0;
        squarePtr++;
    }
//Assign zero to first byte of mySquare.breadth
    *squarePtr=0;
}
void func2(int *ptr) {
    *ptr = 0; 
}

int* func1(void) {
    int ret = 0;
    return &ret ;
}
void main(void) {
    int* ptr = func1() ;
    func2(ptr) ;
}

次のコードで、ptrret を指します。ret のスコープは func1 に制限されているため、func2 内で ptr がアクセスされると、そのアクセスは無効になります。検証では、*ptr に対してレッドの [不適切にデリファレンスされたポインター] チェックが示されます。

Polyspace Code Prover™ の既定の設定では、ローカル変数にポインターを返す関数は検出されません。このような場合に検出されるようにするには、オプションスコープ外のスタック ポインター デリファレンスを検出 (-detect-pointer-escape)を使用します。

[結果の詳細]:チェック メッセージで、ポインターがスコープ外でアクセスされるローカル変数を指していることが通知されます。

#include <stdlib.h>
#include <stdio.h>
int increment_content_of_address(int base_val, int shift)
{
    int j;
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) return 0;

    *pi = base_val;
    
    if(shift < 0) {
       free(pi);
    }

    j = *pi + shift;

    return j;
}

この例では、引数 shift が負である場合、割り当てられたメモリが関数 free() を使用して解放された後に、ポインター pi がデリファレンスされます。[不適切にデリファレンスされたポインター] チェックは、この問題を検出して、潜在的なランタイム エラーを報告することができます (オレンジ チェック)。

この例では、ポインター ptr1 のエイリアスが条件付きで再割り当てされます。randomVar が true の場合、エイリアス ptr2 は新しい場所を指します。そうでない場合は、ptr1ptr2 の両方が同じ場所を指します。free ステートメントの後の ptr2 のデリファレンスでは、randomVar が false の場合に既に解放されているポインターのデリファレンスが試みられます。Polyspace は、このランタイム エラーのオレンジ チェックを報告します。

int main() {
	volatile int randomVar;
	int *ptr1 = (int *)malloc(sizeof(int));

	void *ptr2 = (void *)ptr1;

	if(randomVar) {
		ptr2 = malloc(sizeof(int));
		assert(ptr2 != 0);
	}

	free(ptr1);
	*(int *)ptr2 = randomVar; // Potential IDP


}

チェック情報

グループ: 静的メモリ
言語: C | C++
頭字語: IDP