メインコンテンツ

AUTOSAR C++14 Rule A3-8-1

An object shall not be accessed outside of its lifetime

説明

ルール定義

An object shall not be accessed outside of its lifetime.

根拠

オブジェクトの有効期間は、それがそのコンストラクターによって作成された時点から始まります。この有効期間は、オブジェクトが削除された時点で終わります。コンストラクターの前またはデストラクターの後に変数にアクセスすると、未定義動作につながる可能性があります。状況によっては、多数の操作が有効期間外のオブジェクトに誤ってアクセスする可能性があります。このような操作の例を以下に示します。

  • 未初期化ポインター:アドレスを割り当てる前のポインターに誤ってアクセスしてしまう場合があるかもしれません。この操作では、有効期間内のオブジェクトにアクセスした結果、予測不能なメモリ位置にアクセスすることになります。ベスト プラクティスは、宣言中に nullptr を使用してポインターを初期化することです。

  • 未初期化変数:初期化する前の変数を誤って読み取ってしまう場合があるかもしれません。この操作では、有効期間内のオブジェクトにアクセスした結果、予測不能で役に立たないゴミ値を読み取ることになります。ベスト プラクティスは、宣言中に変数を初期化することです。

  • 既に割り当て解除されたポインターの使用:メモリの割り当て解除後に、ポインターの動的に割り当てられたメモリにアクセスしてしまう場合があるかもしれません。メモリのこのブロックにアクセスしようとすると、有効期間後のオブジェクトにアクセスすることになり、予測不能な動作やセグメンテーション違反すら発生する可能性があります。この問題を解決するには、割り当て解除されたポインターを nullptr に設定してから、アクセスする前にポインターが nullptr かどうかをチェックします。または、生のポインターの代わりに std::unique_ptr を使用します。std::unique_ptr 用に割り当てられたメモリは明示的に割り当て解除する必要がないため、割り当て解除されたメモリに誤ってアクセスすることがなくなります。

  • スコープ外のスタック変数へのポインターまたは参照:非ローカル ポインターをローカル オブジェクトに割り当てる場合があるかもしれません。次に例を示します。

    • 非ローカル ポインターまたはグローバル ポインターが、関数に対してローカルな変数に割り当てられる。

    • ポインターなどの参照渡し関数パラメーターが、関数に対してローカルな変数に割り当てられる。

    • クラスのポインター データ メンバーが、関数に対してローカルな変数に割り当てられる。

    ローカル変数がスコープ外になると、それに対応するメモリ ブロックにゴミ値や予測不能値が格納される可能性があります。このようなメモリ位置へのポインターにアクセスすると、有効期間外のオブジェクトにアクセスすることになり、未定義動作や予測不能動作につながる可能性があります。ベスト プラクティスは、非ローカル ポインターをローカル オブジェクトに割り当てないようにすることです。

  • 有効期間が一時的なオブジェクトの変更:関数呼び出しから返された一時オブジェクトを変更しようとする場合があるかもしれません。一時オブジェクトの変更は、使用しているハードウェアとソフトウェアによってはプログラムの異常終了につながる未定義動作です。ベスト プラクティスは、ローカル変数に一時オブジェクトを割り当ててから、そのローカル変数を変更することです。

有効期間外のオブジェクトにアクセスする可能性のある操作は避けてください。

Polyspace 実装

Polyspace® は、有効期間外のオブジェクトがアクセスされる可能性のある次のようなシナリオをチェックします。

  • 未初期化ポインター: Polyspace は、アクセスする前にアドレスが割り当てられていないポインターにフラグを設定します。

  • 未初期化変数: Polyspace は、値が読み取られる前に初期化されていない変数にフラグを設定します。

  • 既に割り当て解除されたポインターの使用: Polyspace は、たとえば関数 free()delete 演算子を使用してブロックの割り当てを解除した後、そのメモリ ブロックにアクセスする操作にフラグを設定します。

  • スコープ外のスタック変数へのポインターまたは参照: Polyspace は、ポインターまたは参照がスコープ外のローカル変数にフラグを設定します。たとえば、以下の場合にローカル変数にフラグが設定されます。

    • 関数がローカル変数へのポインターを返す

    • グローバル ポインターがローカル変数を指している

    • ポインターなどの参照渡し関数パラメーターがローカル変数を指している

    • クラスのポインター データ メンバーがローカル変数を指している

    Polyspace は、関数定義に含まれるローカル オブジェクトは同じスコープ内にあると仮定します。

  • 有効期間が一時的なオブジェクトへのアクセス: Polyspace は、関数呼び出しから返された一時オブジェクトにアクセスする操作にフラグを設定します。

チェッカーの拡張

チェッカーは次の方法で拡張できます。

トラブルシューティング

ルール違反が想定されるものの、Polyspace から報告されない場合は、コーディング規約違反が想定どおりに表示されない理由の診断を参照してください。

すべて展開する

この例では、アドレスが割り当てられていないポインターへのアクセスに、Polyspace がどのようにフラグを設定するかを示します。

#include <cstdlib>

int* Noncompliant(int* prev)
{
	int j = 42;
	int* pi;
	if (prev == nullptr){
		pi = new int;
		if (pi == nullptr) 
		return nullptr;
	}
	*pi = j; //Noncompliant                    
	return pi;
}
int* Compliant(int* prev)
{
	int j = 42;
	int* pi;
	if (prev == nullptr){
		pi = new int;
		if (pi == nullptr)
		return nullptr;
	} 
	else 
	pi = prev;              
	*pi = j;//Compliant
	return pi;
}
int* AltCompliant(int* prev)
{
	int j = 42;
	int* pi=nullptr;
	if (prev == nullptr){
		pi = new int;
		if (pi == nullptr)
		return nullptr;
	} 
	else              
	if(pi!= nullptr) 
	*pi = j;//Compliant
	return pi;
}

Polyspace は Noncompliant() 内のポインター pi にフラグを設定します。これは、prevNULL ではないときに、pi にアドレスが割り当てられる前にこのポインターがアクセスされるためです。この問題は、さまざまな方法で解決できます。次に例を示します。

  • ステートメント *pi = j の前に pi を初期化します。Compliant() 内の pi への代入にはフラグが設定されません。これは、アクセスされる前に piprev で初期化されるためです。

  • 宣言中に nullptr を使用して pi を初期化します。AltCompliant() 内の pi への代入にはフラグが設定されません。これは、宣言中に pinullptr で初期化されるためです。

この例では、未初期化変数へのアクセスに Polyspace がどのようにフラグを設定するかを示します。

int Noncompliant(void)
{
	extern int getsensor(void);
	int command;
	int val;
	command = getsensor();
	if (command == 2){
		val = getsensor();
	}
	return val;//Noncompliant              
	
}
int Compliant(void)
{
	extern int getsensor(void);
	int command;
	int val=0;//Initialization
	command = getsensor();
	if (command == 2){
		val = getsensor();
	}
	return val;//Compliant              
}

Polyspace は Noncompliant() 内のステートメント return val にフラグを設定します。これは、command2 に等しくない場合に、変数が初期化される前にステートメントが val にアクセスするためです。この問題は、複数の方法で解決できます。たとえば、Compliant() に示すように、宣言中に変数 val を 0 に初期化します。宣言中に変数を初期化することによって、それがすべての実行パスで初期化されるため、ステートメント return val がこのルールに準拠します。

この例では、Polyspace が既に解放されているメモリ ブロックを指している可能性のあるポインターへのアクセスにどのようにフラグを設定するかを示します。

#include <memory>
int Noncompliant(double base_val, double shift){ 
	double j;
	double* pi = new double;
	if (pi == nullptr) 
	return 0;
	*pi = base_val;
	//...
	delete pi;
	//...
	j = *pi + shift;//Noncompliant
	return j;
}
int Compliant(double base_val, double shift){
	double j;
	std::unique_ptr<double>   pi(new double(3.1416));
	if (pi == nullptr) 
	return 0;
	*pi = base_val;
	j = *pi + shift;              
	return j;
}

関数 Noncompliant() では、ポインター pi が演算子 new を使用して宣言および初期化されます。その後、演算子 delete を使用して、動的に割り当てられたメモリが割り当て解除されます。割り当て解除されたポインターが、誤ってステートメント j = *pi + shift; 内でアクセスされます。Polyspace は、このステートメントにフラグを設定します。この問題は、さまざまな方法で解決できます。たとえば、関連するすべての操作を終了してから、割り当てたリソースを割り当て解除したい場合があります。代わりに、生のポインターの代わりにスマート ポインターを使用できます。Compliant() では、ポインター pistd::unique_ptr として宣言されています。pi 用に取得されたリソースは、そのデストラクターを呼び出すことによって、Compliant() の最後で自動的に割り当て解除されます。pi 用に割り当てられたメモリは割り当て解除後にアクセスされないため、Compliant() はこのルールに準拠しています。

この例では、ローカル変数へのポインターがスコープ外に逸脱する可能性のある操作に、Polyspace がどのようにフラグを設定するかを示します。

int* Noncompliant1(void) {
	int ret = 0; //Noncompliant
	return &ret ; 
}
auto Noncompliant2(int var) {
	int rhs = var; //Noncompliant
	auto adder = [&] (int lhs) {
		return (rhs + lhs);
	};
	return adder; 
}
int Compliant1(void) {
	int ret = 0; //Compliant
	return ret ; 
}
auto Compliant2(int var) {
	int rhs = var; //Compliant
	auto adder = [=] (int lhs) {
		return (rhs + lhs);
	};
	return adder; 
}
  • 関数 Noncompliant1() はローカル変数 ret へのポインターを返します。ローカル変数 retNoncompliant() が実行を終了するとすぐに削除されます。返されたポインターは予測不能値を指しています。このような操作はこのルールに準拠していません。この問題を修正するには、Compliant() に示すように、ローカル変数を値で返します。

  • 関数 Noncompliant2() は、ローカル変数 rhs を参照によって取得するラムダ式を返します。この参照は予測不能値をデリファレンスします。これは、関数 Noncompliant2() が実行を終了すると rhs が削除されるためです。この問題を修正するには、Compliant2() に示すように、ローカル変数をラムダ式内のコピーで取得します。

この例では、関数呼び出しによって作成される一時オブジェクトにアクセスする可能性のある操作に、Polyspace がどのようにフラグを設定するかを示します。

#include<vector>
struct S_Array{
	int t;
	int a[5];
};
struct S_Array Factory(void);
std::vector<int> VectorFactory(int aNumber);
int Noncompliant(void) {

	return ++(Factory().a[0]); //Noncompliant
}
int Compliant(void) {
	auto tmp = Factory();
	return ++(tmp.a[0]); //Compliant
}
int Compliant2(void) {
	return ++(VectorFactory(5)[1]); //Compliant
}

Noncompliant() では、Factory() の呼び出しで、一時オブジェクトが作成されます。このオブジェクトの変更はこのルールに準拠していません。Polyspace はステートメント return ++(Factory().a[0]) にフラグを設定します。この問題は、さまざまな方法で解決できます。たとえば、Compliant() に示すように、一時オブジェクトをローカル変数に代入してから変更することができます。または、Compliant2() に示すように、std::vector などのスマート コンテナーを使用します。std::vector などのコンテナーは、独自の有効期間を管理し、移動セマンティクスを持っています。Polyspace はステートメント return ++(VectorFactory(5)[1]); にフラグを設定しません。

チェック情報

グループ: 基本概念
カテゴリ: Required、Non-automated

バージョン履歴

R2020b で導入

すべて展開する