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

daac21c1959343eea2bd68ee5ed63d54.jpgХорошие новости для пользователей 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 they key
}


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

Чтобы найти такой код было проще, в gcc 5.1 и новее на такой код выдается предупреждение и оно включено по умолчанию.

Отдельные читатели уже спешат в комментарии, чтобы рассказать о кривизне рук авторов кода из примеров выше. Тем не менее, проблема настолько распространенная, что в код на C++ рекомендуется использовать следующий фокус (отсюда) с шаблонной функцией:

template
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.

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

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

© Habrahabr.ru