Хорошие новости для пользователей gcc — при использовании gcc 5.1 и выше им будет проще быстро находить вот такую распространенную ошибку вычисления размера массива, объявленного как параметр функции:
Хотя параметр и объявлен как массив известного размера, с точки зрения компиляторов C и C++ это указатель типа char*, поэтому sizeof(arr) даст то же значение, что и sizeof(char*) – скорее всего, 4 или 8. Цикл, скорее всего, будет работать не так, как ожидалось.
Другой вариант:
здесь разработчики хотели перезаписать нулями какие-то данные, но из-за ошибки будут перезаписаны только первые несколько байт. Такую ошибку сложнее найти тестированием, чем первую.
Чтобы найти такой код было проще, в gcc 5.1 и новее на такой код выдается предупреждение и оно включено по умолчанию.
Отдельные читатели уже спешат в комментарии, чтобы рассказать о кривизне рук авторов кода из примеров выше. Тем не менее, проблема настолько распространенная, что в коде на C++ рекомендуется использовать следующий фокус (отсюда) с шаблонной функцией:
Использование countOfElements() в коде выше приведет к ошибке компиляции, зато такой код:
скомпилируется и будет работать правильно.
Помимо явного указания sizeof(smth)/sizeof(smth[0]) также используют макрос:
Посмотрим, как новое предупреждение работает в перечисленных случаях. Пробовать будем на gcc.godbolt.org
Сначала выберем в качестве компилятора gcc 4.9.2 – с параметрами по умолчанию предупреждений о неверном вычислении размера не будет ни в одном из примеров. Потом поменяем на gcc 5.1.0 – в примерах с циклом получаем на строку с заголовком цикла
warning: 'sizeof' on array function parameter 'arr' will return size of 'char*' [-Wsizeof-array-argument]
В таком коде:
выдается то же предупреждение. Аналогично и в коде с макросом:
Код с перезаписью тоже дает предупреждение (используем переносимый memset() только для демонстрации):
ПРИБЫЛЬ.
Отдельного внимания заслуживает тот факт, что clang версии 3.0 уже умел выдавать то же самое предупреждение. Об этом в блоге LLVM было некогда сказано, что это специфичное для clang предупреждение и gcc так не умеет. NO MOAR.
Предупреждение включено по умолчанию, разработчикам останется только правильно исправить проблемный код.
Дмитрий Мещеряков,
департамент продуктов для разработчиков
void something( char arr[100] )
{
// this loop is broken
for( size_t index = 0; index < sizeof(arr)/sizeof(arr[0]); index++ ) {
//WHATEVER
}
}
Хотя параметр и объявлен как массив известного размера, с точки зрения компиляторов C и C++ это указатель типа char*, поэтому sizeof(arr) даст то же значение, что и sizeof(char*) – скорее всего, 4 или 8. Цикл, скорее всего, будет работать не так, как ожидалось.
Другой вариант:
void something( char encryptionKey[9000] )
{
// WHATEVER, PROFIT
// this call is broken
SecureZeroMemory( encryptionKey, sizeof(encryptionKey)); // erase the key
}
здесь разработчики хотели перезаписать нулями какие-то данные, но из-за ошибки будут перезаписаны только первые несколько байт. Такую ошибку сложнее найти тестированием, чем первую.
Чтобы найти такой код было проще, в gcc 5.1 и новее на такой код выдается предупреждение и оно включено по умолчанию.
Отдельные читатели уже спешат в комментарии, чтобы рассказать о кривизне рук авторов кода из примеров выше. Тем не менее, проблема настолько распространенная, что в коде на C++ рекомендуется использовать следующий фокус (отсюда) с шаблонной функцией:
template<typename StoredType, size_t Size>
char ( &ArrayElementsCountHelper(StoredType( &Array )[Size]) )[Size];
#define countOfElements(Array) sizeof(ArrayElementsCountHelper (Array))
Использование countOfElements() в коде выше приведет к ошибке компиляции, зато такой код:
char arr[100]
for( size_t index = 0; index < countOfElements(arr); index++ ) {
//WHATEVER
}
скомпилируется и будет работать правильно.
Помимо явного указания sizeof(smth)/sizeof(smth[0]) также используют макрос:
// in a header far, far away...
#define errorProneCountOfElements( arr ) (sizeof(arr)/sizeof((arr)[0]))
for( size_t index = 0; index < errorProneCountOfElements (arr); index++ ) {
//WHATEVER
}
Посмотрим, как новое предупреждение работает в перечисленных случаях. Пробовать будем на gcc.godbolt.org
Сначала выберем в качестве компилятора gcc 4.9.2 – с параметрами по умолчанию предупреждений о неверном вычислении размера не будет ни в одном из примеров. Потом поменяем на gcc 5.1.0 – в примерах с циклом получаем на строку с заголовком цикла
warning: 'sizeof' on array function parameter 'arr' will return size of 'char*' [-Wsizeof-array-argument]
В таком коде:
void somethingExplicitCount( char arr[] )
{
for( size_t index = 0; index < sizeof(arr)/sizeof(arr[0]); index++ ) {
//WHATEVER
}
}
выдается то же предупреждение. Аналогично и в коде с макросом:
void somethingMacroCount( char arr[9000] )
{
for( size_t index = 0; index < errorProneCountOfElements(arr); index++ ) {
//WHATEVER, PROFIT
}
}
Код с перезаписью тоже дает предупреждение (используем переносимый memset() только для демонстрации):
void somethingMemset( char key[9000] )
{
//WHATEVER, PROFIT
memset(key, 0, sizeof(key)); // don't use memset for sensitive data
}
ПРИБЫЛЬ.
Отдельного внимания заслуживает тот факт, что clang версии 3.0 уже умел выдавать то же самое предупреждение. Об этом в блоге LLVM было некогда сказано, что это специфичное для clang предупреждение и gcc так не умеет. NO MOAR.
Предупреждение включено по умолчанию, разработчикам останется только правильно исправить проблемный код.
Дмитрий Мещеряков,
департамент продуктов для разработчиков