exponenta event banner

Правило AUTOSAR C++ 14 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 используются для любых объектов, содержащих дополняющие данные.

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

В качестве исключения 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