AUTOSAR C++14 Rule A12-0-2

Побитовые операции и операции, предполагающие представление данных в памяти, не должны выполняться на объектах

Описание

Определение правила

Побитовые операции и операции, предполагающие представление данных в памяти, не должны выполняться в отношении объектов.

Объяснение

На C++ представление объекта в памяти может включать:

  • Представители данных, объявленные с различными привилегиями доступа

  • Представители данных битового поля

  • Заполнение байтов между представителями данных

  • Заполнение байтов в конце представителей данных

  • Указатели на vtable для поддержки виртуальных функций

Расположение этих различных частей объекта в памяти зависит от окружения. В сложение статические представители данных или представителей функций объекта хранятся в отдельном физическом местоположении в памяти. Когда вы выполняете побитовую операцию над объектом, принимая определенное расположение данных в памяти, вы можете непреднамеренно принять неправильно, и биты доступа не являются частью представления значения объекта. Доступ к этим битам может привести к неопределенному поведению.

Допустим, этот класс, который содержит виртуальную функцию:

class notPOD{
public:
	virtual void foo();
	int value;
protected:
	double dvalue;

};
//... 
int main(){
	notPOD Obj;
	std::memset(&Obj, 57, 2); // attempts to set Obj::value to 57
}
Когда Obj хранится в блоке памяти, блок содержит указатель на виртуальную таблицу в дополнение к переменным Obj::value и Obj::dvalue. Размер этого указателя или его местоположение в памяти могут зависеть от окружения. В main(), std::memset() пытается задать значение Obj::value принимая, что:

  • Obj::value является первым блоком в представлении памяти Obj.

  • Obj::value представлен 2 байтами в памяти.

Потому что эти предположения, как правило, не верны, использование std::memset() может привести к неопределенному поведению. Например, если вы непреднамеренно измените указатель на виртуальную таблицу, вызывая foo() может вызвать неожиданную функцию.

Представление класса и структур в памяти зависит от среды и может содержать дополнительные байты наряду с представлением значения. Опора на представление данных объекта для выполнения побитовых операций может привести к изменению бит, которые не являются частью представления значения, приводя к неопределенному поведению. Избегайте операций, которые предполагают определенное представление объекта в памяти для доступа к его битам. Чтобы выполнить операции над классом, используйте выделенные функции представителя, перегруженные операторы или мутаторы.

Реализация Polyspace

Функции C, которые получают доступ к битам памяти, включают std::memset(), std::memcpy(), std::memmove(), std::strcpy(), std::memcmp(), std::strcmp(). Polyspace® помечает оператор, когда:

  • Вы используете функции C, чтобы инициализировать или скопировать инициализацию нетривиальных объектов

  • Вы используете функции C, чтобы сравнить нестандартные объекты размещения

  • Вы используете функции C для любых объектов, которые содержат данные заполнения

Операторы, содержащие несоответствующие операции, помечены, и соответствующие объявления классов подсвечены. Определения тривиальных и стандартных классов размещения см. в Стандартах C++, [класс], абзацы 6 и 7 соответственно.

В качестве исключения Polyspace не помечает операции, которые используют функции C для доступа к битам памяти тривиальных и стандартных объектов размещения без данных заполнения. Хотя использование побитовой операции на тривиальных и стандартных классах размещения без данных заполнения соответствует этому правилу, это не является хорошей практикой. Вместо этого используйте выделенную функцию представителя, перегруженные операторы или мутаторы.

Поиск и устранение проблем

Если вы ожидаете нарушения правил, но не видите его, обратитесь к разделу «Стандартные нарушения кодирования не отображаются».

Примеры

расширить все

Рассмотрим этот код, который содержит эти классы:

  • TrivialClass является тривиальным классом с данными заполнения и перегруженным operator|=.

  • NonTrivialClass является нетривиальным классом с виртуальной функцией и перегруженным operator==.

Эти классы по-разному представлены в памяти. В этом примере показано, как Polyspace помечает побитовые операции, которые выполняются для таких объектов.

#include <cstdint>
#include <cstring>
class TrivialClass
{
public:
	TrivialClass() = default;
	TrivialClass(uint8_t c, uint32_t i, int8_t d) :
	c(c), i(i), d(d) {}
	TrivialClass& operator |=(const TrivialClass& other)
	{
		uint32_t buf[4] {this->c|other.c,this->i|other.i,this->d|other.d};
		memcpy(this, buf, sizeof(uint32_t) * 3); //Noncompliant
		return *this;
	}
	
private:
	uint8_t c;
	uint32_t i;
	int8_t d;
};

class NonTrivialClass 
{
public:
	NonTrivialClass() = default;
	NonTrivialClass(uint32_t a, uint32_t b, uint32_t c) : 
	a(a), b(b), c(c){}
	bool operator==(const NonTrivialClass& rhs) const noexcept
	{
		return a==rhs.a && b==rhs.b && c==rhs.c;
	}
	virtual ~NonTrivialClass() {} 
private:
	uint32_t a;
	uint32_t b;
	uint32_t c;
};

int main(void)
{
	TrivialClass A, A1{3,5,7};
	NonTrivialClass B, B1{10,11,12};
	std::memset(&A, 3, 1); //Noncompliant
	A |= A1; 
	if (!std::memcmp(&A, &A1, sizeof(TrivialClass))) {} //Noncompliant
	std::memcpy(&B, &B1, sizeof(NonTrivialClass)); //Noncompliant
	if (B == B1){} //Compliant
	return 0;
}

  • Polyspace помечает оператор std::memset(&A, 3, 1); потому что в этом операторе std::memset() изменяет отдельные биты в представлении памяти тривиального объекта A включая данные заполнения. Доступ к битам данных заполнения объекта является нарушением этого правила, даже если объект является тривиальным объектом класса. По этой же причине Polyspace помечает оператора в определении TrivialClass::operator|= содержащие memcopy().

  • Polyspace помечает оператор std::memcpy(&B, &B1, sizeof(NonTrivialClass)); потому что std::memcpy() обращается к отдельным битам в представлении памяти нетривиального объекта B включая указатель на vtable. Этот указатель не является частью представления значения, и доступ к этому указателю является нарушением этого правила.

  • Polyspace не помечает оператор if(B==B1) потому что NonTrivialClass имеет перегруженный operator== которые могут сравнить B и B1 без доступа к их отдельным битам.

Проверяйте информацию

Группа: Специальные функции представителей
Категория: Требуемая, Частично автоматизированная
Введенный в R2020b