Топ контрибуторов
loading
loading
Знаете ли Вы, что

Лучшие IT работодатели регулярно просматривают рейтинги и профили пользователей в поисках кандидатов. Для корректного отображения ваших данных рекомендуем заполнить ваш профиль и добавить информацию о вас и вашей профессии.

Лента обновлений
ссылка Oct 22 23:24
Комментарий от alexcei88:
В вопросе переменная s даже не выводиться, а выводитьс...
ссылка Oct 22 17:46
Комментарий от AlexFurm:
Это UB, так можно вызывать только статические функции ч...
ссылка Oct 22 17:43
Комментарий от AlexFurm:
Любые битовые операции с signed это UB
ссылка Oct 21 20:30
Комментарий от yoori:
Любой вариант скомпилируется если компилировать не в конеч...
ссылка Oct 21 16:53
Добавлен вопрос в тест QA (Quality Assurance)
Статистика

Тестов: 153, вопросов: 8596. Пройдено: 443367 / 2177508.

Статические проверки в C++ (static assertions c++)

head tail Статья
категория
C++
дата15.05.2009
авторanalizer
голосов11

Все помнят как в школе при решении задач обязательно было делать проверку?

Рекомендуемая литература:

Проверки

В своей прошлой статье про перечисления я писал что оправданием для того чтобы записывать отображение для элементов перечисления вручную может быть только обязательное наличие статической проверки на размер перечисления и массива отображения. Вспомним, у нас было перечисление типа:

enum color{ RED, GREEN, BLUE };
а также массив строковых представлений для каждого элемента перечисления, внутри некоторой функции отображения:
const char* mapping(color c)
{
	const char* const strs[]={"Red as an egypt rose", "Green peace", "Blue Screen of Death"};
	return strs[c];
}
Собственно всё достаточно просто и понятно.... вам понятно. А теперь представьте себе, что другой человек добавил в перечисление новый элемент:
enum color{ RED, GREEN, BLUE, BRIGHTNESS };

Внимание вопрос:

  1. Откуда этому человеку знать о вашей функии mapping(), которая объявлена в другом модуле?
  2. Сколько времени вы потратите всей конторой, когда будете искать почему у заказчика падает сценарий не покрытый вашими тестами, который всего лишь использует этот новый член перечисления при вызове функции mapping()?

Как быть? Всё достаточно просто - нужно всего лишь проверять, что размер массива отображений подходит вашему перечислению. Т.е. в перечисление нужно добавить фиктивный член который будет замыкать перечисление:

enum color{ RED, GREEN, BLUE, BRIGHTNESS, COLOR_END };
А в функцию - соответствующую проверку:
const char* mapping(color c)
{
	const char* const strs[]={"Red as an egypt rose", "Green peace",
		"Blue Screen of Death"};
	if(sizeof(strs)/sizeof(strs[0]) != COLOR_END)
		throw std::runtime_error("Color mapping table length mismatch");
	return strs[c];
}

Теперь вы хотя бы увидите meaningful сообщение об ошибке в логах своей программы и поиск ошибки займёт не так много времени, к тому же если у вас есть хоть один тест использующий эту функцию, то он выявит ошибку. Но это всё - всё равно требует тестирования. Можно ли сделать так чтобы ошибка не вышла за пределы компьютера отдельно взятого программиста, даже без покрытия этой функии тестами? Можно - об этом и поговорим.

Статические проверки без использования возможностей C++

Рассмотрим ещё раз то условие, которое мы проверяем:

sizeof(strs)/sizeof(strs[0]) != COLOR_END
Слева стоит арифметическое выражение численные значения операндов которого известны в момент компиляции программы, соответственно и численное значение выражения слева известно в момент компиляции. Ну а справа - просто константа, значение которой также известно в момент компиляции. Таким образом и результат проверки известен ещё во время компиляции, а значит и проверку можно делать во время компиляции и даже попытаться запретить успешно компилировать программу если проверка не пройдена. Достаточно добавить в код нечто что будет компилироваться если условия равенства выполняется и не будет компилироваться в противном случае. Например вот это:
const char* mapping(color c)
{
	const char* const strs[]={"Red as an egypt rose", "Green peace", "Blue Screen of Death"};
	char ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH[ sizeof(strs)/sizeof(strs[0]) == COLOR_END ];
	return strs[c];
}
Таким образом если значение COLOR_END отличается от количества элементов в массиве - выражение в скобках будет ложным, т.е. интепретировано как ноль и компилятор выдаст ошибку о том что невозможно создать массив нулевого размера:

Comeau 4.3.10.1

error: the size of an array must be greater than zero
	char ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH[sizeof(strs)/sizeof(strs[0]) == COLOR_END];

Visual C++ 2008 Express

error C2466: cannot allocate an array of constant size 0
error C2133: 'ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH' : unknown size
Уверен, многие пользователи GCC скажут:

Что за отстой!? У меня стоит последняя версия ГЦЦ и она это компилирует! Вижуал С++ - не эталон правильности, а о компиляторе Камо я вообще первый раз слышу!

Да всё верно, компилирует, но это - проблема именно ГЦЦ. Стандарт по поводу массивов гласит (переводить не стану, дабы меня не упрекнули в неточном переводе):

§8.3.4

In a declaration T D where D has the form
    D1 [constant-expressionopt]
and the type of the identifier in the declaration T D1 is “derived-declarator-type-list T,” then the type of the identifier of D is an array type. T is called the array element type; this type shall not be a reference type, the (possibly cv-qualified) type void, a function type or an abstract class type. If the constant-expression (5.19) is present, it shall be an integral constant expression and its value shall be greater than zero.

На самом деле - не беда, обойти эту вольность в компиляторе можно достаточно просто, нужно просто создавать массив не нулевого, а отрицательного размера (или же указывать для ГЦЦ параметр компиляции -pedantic-errors):

char ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH[ sizeof(strs)/sizeof(strs[0]) == COLOR_END ? 1 : -1 ];

Тогда мы получаем следующий результат:

Comeau 4.3.10.1

error: the size of an array must be greater than zero
	char ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH[sizeof(strs)/sizeof(strs[0]) == COLOR_END ? 1 : -1 ];

Visual C++ 2008 Express

error C2118: negative subscript

GCC 4.3.0 (MinGW)

In function 'const char* mapping(color)':
error: size of array 'ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH' is negative

Как видите теперь это работает и под ГЦЦ, но для Вижуал С++ название переменной (которое содержит описание ошибки) теперь не выводится. У данного варианта статической проверки с использованием массивов есть ещё один минус - когда проверка проходит успешно, вы получаете предупреждение от комилятора о том что переменная была объявлена, но никогда не используется. Это предупреждение устраняется довольно просто - нужно всего лишь декларировать не массив, а тип:

typedef char ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH[ sizeof(strs)/sizeof(strs[0]) == COLOR_END ? 1 : -1 ];

Итого имеем отклонение от идеального поведения (потеря названия или же предупреждение) для одного компилятора из трёх тестируемых. Результат не плохой, но, например, мне использовать Камо практически не приходится, а вот Вижуал С++ я использую не реже ГЦЦ, поэтому двигаемся дальше. Для тех кто работает в чистом Си и хочет использовать подобные проверки, способ описанный выше наверное единственный (другого я не знаю).

Статические проверки

Хватит впрочем ходить вокруг да около - выше я указал какими путями мы шли к решению, и недостатки тех или иных решений. Сейчас просто покажу решение которое использую сейчас. Принцип действия схож описанному Александреску в Совеременном программировании на С++, но отличается тем, что может быть использовано в глобальной области видимости, т.е. может быть использовано вне функций, а так же генерирует (псевдо)уникальные имена, что позволяет не задумываться о том что имена классов проверок могут совпасть и вызвать ошибку компиляции. Для универсальности определим макрос, который будет принимать в себя проверяемое выражение и сообщение об ошибке:

#include <boost/preprocessor/cat.hpp>
namespace static_check{
	template<bool>struct check{inline check(){}};
	template<>struct check<false>;
}
#define STATIC_CHECK(expr,msg) \
	static_check::check<(expr!=0)> \
	BOOST_PP_CAT(BOOST_PP_CAT(ERROR__,msg),BOOST_PP_CAT(_at_line_,__LINE__))

Теперь проверка в функции выглядит так:

const char* mapping(color c)
{
	const char* const strs[]={"Red as an egypt rose", "Green peace", "Blue Screen of Death"};
	STATIC_CHECK(sizeof(strs)/sizeof(strs[0]) == COLOR_END, ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH);
	return strs[c];
}

Тогда сообщения об ошибке будут следующими:

Comeau 4.3.10.1

error: incomplete type is not allowed
	STATIC_CHECK(sizeof(strs)/sizeof(strs[0]) == COLOR_END, ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH);

Visual C++ 2008 Express

error C2079: 'ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH_at_line_14' uses undefined struct
'static_check::check<false>'

GCC 4.3.0 (MinGW)

In function 'const char* mapping(color)':
error: aggregate 'static_check::check<false> ERROR__ENUM_COLOR_AND_ARRAY_SIZE_MISMATCH_at_line_14' has incomplete
type and cannot be defined

Итак... что же собственно происходит? Ничего особенного - если условие выполняется, то мы просто создаём переменную типа check<true>, в противном же случае - переменную незавершённого типа check<false>, которая создана быть не может. Куда же делось предупреждение о том что эта самая переменная создаётся, но не используется? Да никуда, просто переменная используется, пускай и только в момент конструирования объекта, когда вызывается нетривиальный конструктор по умолчанию (да, для компилятора он нетривиален).

Велосипед

Всё уже украдено, до нас (с) Операция Ы

... который был опять изобретён. Если вам надо просто производить статическую ошибку, не обязательно выводить её описание и вы не любите чужие велосипеды - сделайте проще:

#include <boost/static_assert.hpp>
const char* mapping(color c)
{
	const char* const strs[]={"Red as an egypt rose", "Green peace", "Blue Screen of Death"};
	BOOST_STATIC_ASSERT(sizeof(strs)/sizeof(strs[0]) == COLOR_END);
	return strs[c];
}

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


Собственно, я закончил. Что хотел - то рассказал, кому надо было - тот понял. Рацпредложения по улучшению - велкам.

----

Дмитрий analizer Потапов, март-апрель 2009

Если Вам понравилась статья, проголосуйте за нее

Голосов: 11  loading...
admin   c0nst   md6   alexis112   anEcho   valyala   serj   Qivan   DTSAR   rihtar   violet23