PDA

Просмотр полной версии : [C++] Псевдооператор arraysize



Daniel_Cortez
25.10.2016, 20:44
Думаю, те из вас, кто работал с исходниками Pawn (или с SDK для плагинов к SA-MP), могли заметить макрос arraysize в файле amx.h:


#define arraysize(array) (sizeof(array) / sizeof((array)[0]))

Работает он следующим образом: "sizeof(array)" возвращает размер всего массива (в байтах), а "sizeof(array[0])" - 0-го элемента, в результате деления получается количество элементов в массиве.
Пример использования:


#include <stdio.h>
#include <stdint.h>

#define arraysize(array) (sizeof(array) / sizeof((array)[0]))

int main(int argc, char **argv)
{
uint16_t array[10];
// Значение, возвращаемое макросом arraysize имеет тип size_t, поэтому лучше явно привести его к типу int.
const int size = (int)arraysize(array);
// sizeof(array) = 20, sizeof(array[0]) = 2, 20 / 2 = 10
printf("size = %d\n", size); // Выведет "size = 10".
return 0;
}

Конечно же, этим макросом пользоваться куда удобнее, чем 2 раза писать "sizeof" с названием одного и того же массива.
Однако, здесь есть один подводный камень: макрос никак не может отличить массив от указателя.
Допустим, у вас есть такой код:


#include <stdio.h>
#include <stdint.h>

#define arraysize(array) (sizeof(array) / sizeof((array)[0]))

void PrintArraySize(uint16_t array[])
{
const int x = (int)arraysize(array);
printf("x = %d\n", x);
}

int main(int argc, char **argv)
{
uint16_t array[10];
PrintArraySize(array); // Выведет "size = 2".
return 0;
}

В этом примере функции PrintArraySize вместо всего массива передаётся только указатель на него.
Выражение "sizeof(array[0])" вернёт 2, как и положено, но "sizeof(array)" вернёт размер не массива, а указателя - на 32-разрядных платформах это 4 байта, и в итоге макрос arraysize(array) выдаст всего 4/2 = 2 элемента вместо 10.
Таким образом в ваш код легко может вкрасться ошибка, если вы случайно передадите в arraysize указатель вместо массива. В лучшем случае компилятор выдаст предупреждение, которое может остаться незамеченным среди других сообщений в логе построения.

Каким образом решить эту проблему?
Для чистого C решения я не нашёл (не факт, что для этого языка оно вообще есть), но в C++ можно использовать механизм шаблонов.


template <typename T, size_t size>
size_t arraysize(const T (&)[size])
{
return size;
}

Эта функция узнаёт размер массива на этапе компиляции, как в случае с макросом (т.е. технически никакого вызова функции не будет), но передать ей указатель не получится.
Рассмотрим работу функции на примере передачи в неё массива:


#include <stdio.h>
#include <stdint.h>

template <typename T, size_t size> size_t arraysize(const T (&)[size]){ return size; }

int main(int argc, char **argv)
{
uint16_t array[10];
const int size = (int)arraysize(array);
printf("size = %d\n", size); // Выведет "size = 10".
return 0;
}

Здесь функция работает так, как и положено, возвращая количество элементов в массиве.
Но что будет если передать в неё указатель?


#include <stdio.h>
#include <stdint.h>

template <typename T, size_t size> size_t arraysize(const T (&)[size]){ return size; }

void PrintArraySize(uint16_t array[])
{
const int size = (int)arraysize(array); // error: no matching function for call to 'arraysize(uint16_t*&)'
printf("size = %d\n", x);
}

int main(int argc, char **argv)
{
uint16_t array[10];
PrintArraySize(array);
return 0;
}

Компилятор выдаёт ошибку при попытке передать в arraysize указатель, поэтому любые случайности с вычислением размера указателя исключаются.

Работа функции проверена на следующих компиляторах: Visual C++, GCC (в.т.ч. MinGW), Clang.


Статью подготовил: Daniel_Cortez (http://pro-pawn.ru/member.php?100-Daniel_Cortez)


Специально для Pro-Pawn.ru (http://www.pro-pawn.ru)
Копирование данной статьи на других ресурсах без разрешения автора запрещено!

VVWVV
25.12.2016, 12:21
Кстати, можно ещё добавить оператор constexpr. Как минимум, он покажет, что функция и значение в ней выполняется на этапе компиляции.


template <class _Ty, size_t size> constexpr size_t arraysize(_Ty(&)[size]) { return size; }


Кроме того, в примере допущена ошибка: нет переменной X. Исправленный код:


#include <stdio.h>
#include <stdint.h>

template <typename T, size_t size> size_t arraysize(T (&)[size]){ return size; }

void PrintArraySize(uint16_t array[])
{
const int size = (int)arraysize(array); // error: no matching function for call to 'arraysize(uint16_t*&)'
printf("size = %d\n", size);
}

int main(int argc, char **argv)
{
uint16_t array[10];
PrintArraySize(array);
return 0;
}

Daniel_Cortez
25.12.2016, 13:38
в примере допущена ошибка: нет переменной X
Спасибо, исправил.



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