ABBYY corporate blog
C++
Compilers
C
27 July 2015

Новое предупреждение о неверном вычислении размера массива в gcc 5.1

Хорошие новости для пользователей gcc — при использовании gcc 5.1 и выше им будет проще быстро находить вот такую распространенную ошибку вычисления размера массива, объявленного как параметр функции:

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.

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

Дмитрий Мещеряков,
департамент продуктов для разработчиков

+40
14.3k 48
Comments 35