Как определить размер моего массива в C?


1032

Как определить размер моего массива в C?

То есть количество элементов, которое может содержать массив?


2
Для безопасности типов см. Stackoverflow.com/questions/19452971/array-size-macro-that-rejects-pointers
TS

3
Поздравляем с достижением 1000 голосов!
SS Anne

Ответы:


1262

Управляющее резюме:

int a[17];
size_t n = sizeof(a)/sizeof(a[0]);

Полный ответ:

Чтобы определить размер вашего массива в байтах, вы можете использовать sizeof оператор:

int a[17];
size_t n = sizeof(a);

На моем компьютере длина целых 4 байта, поэтому n равно 68.

Чтобы определить количество элементов в массиве, мы можем разделить общий размер массива на размер элемента массива. Вы можете сделать это с типом, как это:

int a[17];
size_t n = sizeof(a) / sizeof(int);

и получите правильный ответ (68/4 = 17), но если тип aизменится, у вас будет неприятная ошибка, если вы забудете изменить sizeof(int)также.

Поэтому предпочтительным делителем является sizeof(a[0])или эквивалентен sizeof(*a)размер первого элемента массива.

int a[17];
size_t n = sizeof(a) / sizeof(a[0]);

Еще одним преимуществом является то, что теперь вы можете легко параметризовать имя массива в макросе и получить:

#define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

int a[17];
size_t n = NELEMS(a);

6
Сгенерированный код будет идентичен, так как компилятор знает тип * int_arr во время компиляции (и, следовательно, значение sizeof (* int_arr)). Это будет константа, и компилятор может оптимизировать соответственно.
Марк Харрисон

10
Так должно быть со всеми компиляторами, поскольку результаты sizeof определяются как константа времени компиляции.
Марк Харрисон

451
Важно : не прекращайте читать здесь, прочитайте следующий ответ! Это работает только для массивов в стеке , например, если вы используете malloc () или обращаетесь к параметру функции, вам не повезло. См. ниже.
Маркус

7
Для программирования Windows API на C или C ++ есть ARRAYSIZEопределенный макрос WinNT.h(который добавляется другими заголовками). Таким образом, пользователям WinAPI не нужно определять свои собственные макро.
Луми

17
@Markus работает для любой переменной, имеющей тип массива; это не должно быть "в стеке". Например static int a[20];. Но ваш комментарий полезен для читателей, которые могут не осознавать разницу между массивом и указателем.
ММ

808

Это sizeofправильный путь, если вы имеете дело с массивами, не полученными в качестве параметров. Массив, отправленный в качестве параметра функции, рассматривается как указатель, поэтому sizeofон возвращает размер указателя, а не размер массива.

Таким образом, внутри функции этот метод не работает. Вместо этого всегда передавайте дополнительный параметр, size_t sizeуказывающий количество элементов в массиве.

Тестовое задание:

#include <stdio.h>
#include <stdlib.h>

void printSizeOf(int intArray[]);
void printLength(int intArray[]);

int main(int argc, char* argv[])
{
    int array[] = { 0, 1, 2, 3, 4, 5, 6 };

    printf("sizeof of array: %d\n", (int) sizeof(array));
    printSizeOf(array);

    printf("Length of array: %d\n", (int)( sizeof(array) / sizeof(array[0]) ));
    printLength(array);
}

void printSizeOf(int intArray[])
{
    printf("sizeof of parameter: %d\n", (int) sizeof(intArray));
}

void printLength(int intArray[])
{
    printf("Length of parameter: %d\n", (int)( sizeof(intArray) / sizeof(intArray[0]) ));
}

Вывод (в 64-битной ОС Linux):

sizeof of array: 28
sizeof of parameter: 8
Length of array: 7
Length of parameter: 2

Вывод (в 32-битной ОС Windows):

sizeof of array: 28
sizeof of parameter: 4
Length of array: 7
Length of parameter: 1

10
почему, length of parameter:2если передается только указатель на первый элемент массива?
Bbvarghe

16
@Bbvarghe Это потому, что указатели в 64-битных системах имеют размер 8 байт (sizeof (intArray)), но длина по-прежнему (обычно) 4 байта (sizeof (intArray [0])).
Elideb

13
@Pacerier: правильного кода нет - обычное решение - передать длину вместе с массивом в качестве отдельного аргумента.
Жан Хоминал

10
Подождите, так что нет способа получить доступ к массиву непосредственно из указателя и увидеть его размер? Новое для C здесь.
Судо

7
@Michael Trouw: вы можете использовать синтаксис оператора , если это заставляет вас чувствовать себя лучше: (sizeof array / sizeof *array).
Chqrlie

134

Стоит отметить, что sizeofэто не помогает при работе со значением массива, который распался на указатель: даже если он указывает на начало массива, для компилятора он такой же, как указатель на один элемент этого массива , Указатель не «запоминает» что-либо еще о массиве, который использовался для его инициализации.

int a[10];
int* p = a;

assert(sizeof(a) / sizeof(a[0]) == 10);
assert(sizeof(p) == sizeof(int*));
assert(sizeof(*p) == sizeof(int));

1
@ Magnus: стандарт определяет sizeof как число байтов в объекте, а sizeof (char) всегда равен единице. Количество битов в байте зависит от конкретной реализации. Изменить: стандартный раздел ANSI C ++ 5.3.3 Sizeof: «Оператор sizeof возвращает число байтов в объектном представлении своего операнда. Размеры [char], sizeof (знаковый char) и sizeof (неподписанный char) 1; результат sizeof, примененного к любому другому фундаментальному типу, определяется реализацией. "
Skizz

Раздел 1.6 Модель памяти C ++: «Базовой единицей хранения в модели памяти C ++ является байт. Байт, по крайней мере, достаточно большой, чтобы содержать любой элемент базового набора символов выполнения, и состоит из непрерывной последовательности битов, числа из которых определяется реализацией ".
Skizz

2
Я помню, что у CRAY был C с char32 битами. Все, что говорится в стандарте, заключается в том, что могут быть представлены целочисленные значения от 0 до 127, а его диапазон составляет по крайней мере от -127 до 127 (символ подписан) или от 0 до 255 (символ без знака).
vonbrand

5
Это отличный ответ. Я хочу прокомментировать, что все вышеприведенные утверждения оцениваются как ИСТИНА.
Джавад

49

Размер «трюка» - лучший способ, который я знаю, с одним небольшим, но (для меня это главная любимая мозоль) важным изменением в использовании скобок.

Как ясно видно из статьи в Википедии, С sizeofне является функцией; это оператор . Таким образом, он не требует скобок вокруг своего аргумента, если аргумент не является именем типа. Это легко запомнить, поскольку аргумент выглядит как приведенное выражение, которое также использует круглые скобки.

Итак: если у вас есть следующее:

int myArray[10];

Вы можете найти количество элементов с кодом следующим образом:

size_t n = sizeof myArray / sizeof *myArray;

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

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

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

Например, скажем, у вас есть функция, которая выводит некоторые данные в виде потока байтов, например, по сети. Давайте вызовем функцию send()и заставим ее принимать в качестве аргументов указатель на объект для отправки и количество байтов в объекте. Итак, прототип становится:

void send(const void *object, size_t size);

А затем вам нужно отправить целое число, поэтому вы кодируете его так:

int foo = 4711;
send(&foo, sizeof (int));

Теперь вы ввели тонкий способ выстрелить себе в ногу, указав тип fooв двух местах. Если одно меняется, а другое нет, код ломается. Таким образом, всегда делайте это так:

send(&foo, sizeof foo);

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


Кстати, они одинаковые инструкции на уровне процессора? Требует sizeof(int)меньших инструкций, чем sizeof(foo)?
Pacerier

@Pacerier: нет, они идентичны. Думай о int x = 1+1;против int x = (1+1);. Здесь круглые скобки сугубо просто эстетичны.
Кетцалькоатль

@Aidiakapi Это не правда, рассмотрим C99 VLA.
расслабиться

@unwind Спасибо, я исправлен. Чтобы исправить мой комментарий, sizeofвсегда будет постоянным в C ++ и C89. С массивами переменной длины C99 это может быть оценено во время выполнения.
Aidiakapi

2
sizeofможет быть оператором, но это следует рассматривать как функцию в соответствии с Линусом Торвальдсом. Согласен. Прочитайте его рациональное здесь: lkml.org/lkml/2012/7/11/103
Доктор Лицо Человек II

37
int size = (&arr)[1] - arr;

Проверьте эту ссылку для объяснения


7
Малый придира: результат вычитания указателя имеет тип ptrdiff_t. (Обычно в 64-битной системе это будет больший тип, чем int). Даже если вы измените intна ptrdiff_tв этом коде, он все равно имеет ошибку, если arrзанимает более половины адресного пространства.
ММ

2
@MM Еще одна небольшая хитрость: в зависимости от архитектуры вашей системы адресное пространство не так велико, как размер указателя в большинстве систем. Например, Windows ограничивает адресное пространство для 64-битных приложений 8 ТБ или 44 битами. Так что, даже если у вас есть массив, например, больше половины вашего адресного пространства 4,1 ТБ, это не будет ошибкой. Только если ваше адресное пространство превышает 63 бита в этих системах, возможно даже столкнуться с такой ошибкой. В общем, не беспокойся об этом.
Aidiakapi

1
@Aidiakapi в 32-разрядной системе x86 Linux или в Windows с /3Gвозможностью разделения пользователя / ядра 3G / 1G, что позволяет иметь размер массивов до 75% от размера адресного пространства.
Руслан

1
Рассмотрим в foo buf1[80]; foo buf2[sizeof buf1/sizeof buf1[0]]; foo buf3[(&buf1)[1] - buf1];качестве глобальных переменных. buf3[]декларация терпит неудачу, поскольку (&buf1)[1] - buf1не является константой.
chux - Восстановить Монику

2
Это технически неопределенное поведение, поскольку стандарт явно запрещает разыменование после конца массива (даже если вы не пытаетесь прочитать сохраненное значение)
ММ

26

Вы можете использовать оператор sizeof, но он не будет работать для функций, поскольку он будет использовать ссылку на указатель. Чтобы найти длину массива, выполните следующие действия:

len = sizeof(arr)/sizeof(arr[0])

Код, изначально найденный здесь: C программа для поиска количества элементов в массиве


21

Если вы знаете тип данных массива, вы можете использовать что-то вроде:

int arr[] = {23, 12, 423, 43, 21, 43, 65, 76, 22};

int noofele = sizeof(arr)/sizeof(int);

Или, если вы не знаете тип данных массива, вы можете использовать что-то вроде:

noofele = sizeof(arr)/sizeof(arr[0]);

Примечание. Это работает, только если массив не определен во время выполнения (например, malloc) и массив не передан в функцию. В обоих случаях arr(имя массива) является указателем.


4
int noofele = sizeof(arr)/sizeof(int);только наполовину лучше, чем кодирование int noofele = 9;. Использование sizeof(arr)поддерживает гибкость при изменении размера массива. Все же sizeof(int)нуждается в обновлении, если тип arr[]изменения. Лучше использовать, sizeof(arr)/sizeof(arr[0])даже если тип хорошо известен. Непонятно, почему используется intдля noofeleпротив size_t, тип, возвращаемый sizeof().
chux - Восстановить Монику

19

Макрос, ARRAYELEMENTCOUNT(x)который все используют, оценивает неправильно . На самом деле, это просто деликатный вопрос, потому что у вас не может быть выражений, которые приводят к типу «массив».

/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x[0]))

ARRAYELEMENTCOUNT(p + 1);

На самом деле оценивается как:

(sizeof (p + 1) / sizeof (p + 1[0]));

В то время как

/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x)[0])

ARRAYELEMENTCOUNT(p + 1);

Он правильно оценивает:

(sizeof (p + 1) / sizeof (p + 1)[0]);

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


Это правильно; мой пример был плохим. Но это именно то, что должно произойти. Как я упоминал ранее p + 1, в конечном итоге это будет указатель типа и сделает недействительным весь макрос (как если бы вы пытались использовать макрос в функции с параметром указателя).

В конце дня, в данном конкретном случае, ошибка на самом деле не имеет значения (так что я просто трачу время каждого; хазз!), Потому что у вас нет выражений с типом «массив». Но на самом деле вопрос о тонкостях оценки препроцессора, я думаю, является важным.


2
Спасибо за объяснение. Исходная версия приводит к ошибке во время компиляции. Clang сообщает, что «подписанное значение не является массивом, указателем или вектором». Это кажется предпочтительным поведением в этом случае, хотя ваши комментарии о порядке оценки в макросах хорошо приняты.
Марк Харрисон

1
Я не думал о жалобе компилятора как об автоматическом уведомлении неверного типа. Спасибо!

3
Есть ли причина не использовать (sizeof (x) / sizeof (*x))?
Серьезный

16

Для многомерных массивов это немного сложнее. Часто люди определяют явные макроконстанты, т.е.

#define g_rgDialogRows   2
#define g_rgDialogCols   7

static char const* g_rgDialog[g_rgDialogRows][g_rgDialogCols] =
{
    { " ",  " ",    " ",    " 494", " 210", " Generic Sample Dialog", " " },
    { " 1", " 330", " 174", " 88",  " ",    " OK",        " " },
};

Но эти константы могут быть оценены и во время компиляции с помощью sizeof :

#define rows_of_array(name)       \
    (sizeof(name   ) / sizeof(name[0][0]) / columns_of_array(name))
#define columns_of_array(name)    \
    (sizeof(name[0]) / sizeof(name[0][0]))

static char* g_rgDialog[][7] = { /* ... */ };

assert(   rows_of_array(g_rgDialog) == 2);
assert(columns_of_array(g_rgDialog) == 7);

Обратите внимание, что этот код работает на C и C ++. Для массивов с более чем двумя измерениями используйте

sizeof(name[0][0][0])
sizeof(name[0][0][0][0])

и т.д. до бесконечности.


15
sizeof(array) / sizeof(array[0])

В зависимости от типа arrayимеет, вам не нужно использовать , sizeof(array) / sizeof(array[0])если arrayэто массив либо char, unsigned charили signed char- Цитата из C18,6.5.3.4 / 4: «Когда SizeOf применяются к операнду , который имеет тип CHAR, беззнаковый символ или символ подписанный , (или его квалифицированная версия) результат равен 1 ». В этом случае вы можете просто сделать, sizeof(array)как описано в моем ответе .
RobertS поддерживает Монику Челлио

15

Размер массива в C:

int a[10];
size_t size_of_array = sizeof(a);      // Size of array a
int n = sizeof (a) / sizeof (a[0]);    // Number of elements in array a
size_t size_of_element = sizeof(a[0]); // Size of each element in array a                                          
                                       // Size of each element = size of type

4
Любопытно , что код , используемый size_t size_of_elementеще intс int n = sizeof (a) / sizeof (a[0]); и неsize_t n = sizeof (a) / sizeof (a[0]);
chux - восстановят Монику

1
Привет @Yogeesh HT, не могли бы вы ответить на вопрос о Chux. Мне также очень любопытно узнать, как int n = sizeof (a) / sizeof (a [0]) дает длину массива и почему мы не используем size_t для длины массива. Кто-нибудь может ответить на это?
Мозг

1
@Brain sizeof (a) дает sizeof всех элементов, присутствующих в массиве, а sizeof (a [0]) дает sizeof 1-го элемента. Предположим, а = {1,2,3,4,5}; sizeof (a) = 20 байт (если sizeof (int) = 4 байт умножить на 5), sizeof (a [0]) = 4 байт, поэтому 20/4 = 5, т.е. нет элементов
Йогиш HT

2
@YogeeshHT Для очень больших массивов нравится char a[INT_MAX + 1u];, int nкак он используется в int n = sizeof (a) / sizeof (a[0]);недостаточном (это УБ). Использование size_t n = sizeof (a) / sizeof (a[0]);не влечет за собой этой проблемы.
chux - Восстановить Монику

13

Я бы посоветовал никогда не использовать sizeof(даже если это можно использовать) для получения любого из двух разных размеров массива, либо по количеству элементов, либо в байтах, которые являются двумя последними случаями, которые я здесь показываю. Для каждого из двух размеров макросы, показанные ниже, могут быть использованы для повышения безопасности. Причина заключается в том, чтобы сделать понятным намерение кода для сопровождающих, и отличие sizeof(ptr)от sizeof(arr)на первый взгляд (которое написано таким образом не очевидно), чтобы ошибки были очевидны для всех, кто читает код.


TL; DR:

#define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]) + must_be_array(arr))

#define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))

#define ARRAY_BYTES(arr)    (sizeof((arr)[0]) * ARRAY_SIZE(arr))

must_be_array(arr)(определено ниже) IS необходим, так как -Wsizeof-pointer-divглючит (по состоянию на апрель / 2020):

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")
#define must_be_array(a)        (                                       \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        Static_assert_array(a);                         \
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)

В этой теме были обнаружены важные ошибки: https://lkml.org/lkml/2015/9/3/428.

Я не согласен с решением, которое предлагает Линус, которое никогда не использует нотацию массива для параметров функций.

Мне нравится обозначение массива как документация, что указатель используется в качестве массива. Но это означает, что необходимо применять надежное решение, чтобы невозможно было написать код с ошибками.

Из массива у нас есть три размера, которые мы могли бы знать:

  • Размер элементов массива
  • Количество элементов в массиве
  • Размер в байтах, который массив использует в памяти

Размер элементов массива

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

Пример использования:

void foo(ptrdiff_t nmemb, int arr[static nmemb])
{
        qsort(arr, nmemb, sizeof(arr[0]), cmp);
}

qsort() нужно это значение в качестве третьего аргумента.


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


Количество элементов в массиве

Этот является наиболее распространенным, и многие ответы предоставили вам типичный макрос ARRAY_SIZE:

#define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]))

Учитывая, что результат ARRAY_SIZE обычно используется со знаковыми переменными типа ptrdiff_t, полезно определить подписанный вариант этого макроса:

#define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))

Массивы с более чем PTRDIFF_MAXодним членом будут давать недопустимые значения для этой подписанной версии макроса, но после чтения C17 :: 6.5.6.9 подобные массивы уже играют с огнем. Только ARRAY_SIZEи size_tследует использовать в этих случаях.

Последние версии компиляторов, такие как GCC 8, будут предупреждать вас, когда вы применяете этот макрос к указателю, поэтому он безопасен (есть другие способы сделать его более безопасным со старыми компиляторами).

Он работает путем деления размера в байтах всего массива на размер каждого элемента.

Примеры использования:

void foo(ptrdiff_t nmemb)
{
        char buf[nmemb];

        fgets(buf, ARRAY_SIZE(buf), stdin);
}

void bar(ptrdiff_t nmemb)
{
        int arr[nmemb];

        for (ptrdiff_t i = 0; i < ARRAY_SSIZE(arr); i++)
                arr[i] = i;
}

Если бы эти функции не использовали массивы, а вместо этого получили их в качестве параметров, прежний код не скомпилировался бы, поэтому было бы невозможно иметь ошибку (учитывая, что используется последняя версия компилятора или что используется какой-то другой прием) и нам нужно заменить вызов макроса значением:

void foo(ptrdiff_t nmemb, char buf[nmemb])
{

        fgets(buf, nmemb, stdin);
}

void bar(ptrdiff_t nmemb, int arr[nmemb])
{

        for (ptrdiff_t i = 0; i < nmemb; i++)
                arr[i] = i;
}

Размер в байтах, который массив использует в памяти

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

Обычный способ получить это значение - использовать sizeof(arr). Проблема: та же, что и с предыдущей; если у вас есть указатель вместо массива, ваша программа сойдет с ума.

Решение проблемы включает использование того же макроса, что и раньше, который, как мы знаем, безопасен (он нарушает компиляцию, если он применяется к указателю):

#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

Как это работает, очень просто: оно отменяет деление, которое ARRAY_SIZEпроисходит, поэтому после математических отмен вы получите только один sizeof(arr), но с дополнительной безопасностью ARRAY_SIZEконструкции.

Пример использования:

void foo(ptrdiff_t nmemb)
{
        int arr[nmemb];

        memset(arr, 0, ARRAY_BYTES(arr));
}

memset() нужно это значение в качестве третьего аргумента.

Как и раньше, если массив получен как параметр (указатель), он не будет компилироваться, и нам придется заменить вызов макроса значением:

void foo(ptrdiff_t nmemb, int arr[nmemb])
{

        memset(arr, 0, sizeof(arr[0]) * nmemb);
}

Обновление (23 апреля / 2020): -Wsizeof-pointer-divглючит :

Сегодня я узнал, что новое предупреждение в GCC работает, только если макрос определен в заголовке, который не является системным заголовком. Если вы определяете макрос в заголовке, который установлен в вашей системе (обычно /usr/local/include/или /usr/include/) ( #include <foo.h>), компилятор НЕ выдаст предупреждение (я попробовал GCC 9.3.0).

Итак, мы #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))хотим и хотим сделать это безопасным. Нам понадобится C11 _Static_assert()и некоторые расширения GCC: операторы и объявления в выражениях , __builtin_types_compatible_p :

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (                                       \
{                                                                       \
        Static_assert_array(arr);                                       \
        sizeof(arr) / sizeof((arr)[0]);                                \
}                                                                       \
)

Теперь ARRAY_SIZE()это абсолютно безопасно, и, следовательно, все его производные будут в безопасности.


Обновление: libbsd предоставляет __arraycount():

Libbsd предоставляет макрос __arraycount()in <sys/cdefs.h>, что небезопасно, поскольку в нем отсутствует пара круглых скобок, но мы можем сами добавить эти скобки, и поэтому нам даже не нужно записывать деление в нашем заголовке (зачем дублировать уже существующий код? ). Этот макрос определен в системном заголовке, поэтому, если мы его используем, мы вынуждены использовать макросы выше.

#include <sys/cdefs.h>


#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (                                       \
{                                                                       \
        Static_assert_array(arr);                                       \
        __arraycount((arr));                                            \
}                                                                       \
)

#define ARRAY_SSIZE(arr)        ((ptrdiff_t)ARRAY_SIZE(arr))
#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

Некоторые системы обеспечивают nitems()в <sys/param.h>вместо этого, и в некоторых системах обоих. Вы должны проверить свою систему и использовать ту, которая у вас есть, и, возможно, использовать некоторые условные выражения препроцессора для переносимости и поддержки обоих.


Обновление: разрешить использование макроса в области видимости файла:

К сожалению, ({})расширение gcc не может быть использовано на уровне файла. Чтобы иметь возможность использовать макрос в области видимости файла, статическое утверждение должно быть внутри sizeof(struct {}). Затем умножьте его на, 0чтобы не повлиять на результат. Приведение к (int)может быть полезно для имитации возвращаемой функции (int)0(в этом случае это не обязательно, но затем ее можно использовать для других целей).

#include <sys/cdefs.h>


#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "Not a `[]` !")
#define must_be_array(a)        (                                       \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        Static_assert_array(a);                         \
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)

#define ARRAY_SIZE(arr)         (__arraycount((arr)) + must_be_array(arr))
#define ARRAY_SSIZE(arr)        ((ptrdiff_t)ARRAY_SIZE(arr))
#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

3
Не могли бы вы объяснить, почему понизить голос? Это показывает решение небезопасной и общую конструкцию ( sizeof(arr)), не отображаются в другом месте: ARRAY_BYTES(arr).
Cacahuete Frito

2
ARRAY_SIZE достаточно распространен, чтобы его можно было свободно использовать, а ARRAY_BYTES очень явно указан в своем названии и должен быть определен рядом с ARRAY_SIZE, чтобы пользователь мог видеть как легко, так и по его использованию, я не думаю, что кто-то, читающий код, сомневается в оно делает. Я имел в виду не использовать простое sizeof, а использовать вместо этого эти конструкции; если вам хочется писать эти конструкции каждый раз, вы, скорее всего, совершите ошибку (очень часто, если вы копируете вставить, и также очень часто, если вы пишете их каждый раз, потому что у них много скобок) ...
Cacahuete Frito

3
..., поэтому я стою на главном заключении: одно sizeofявно небезопасно (причины в ответе), и не использование макросов, а использование предоставленных мною конструкций, каждый раз, еще более небезопасно, поэтому единственный путь это макросы.
Cacahuete Frito

3
@MarkHarrison Я знаю разницу между указателями и массивами. Но были времена, когда у меня была функция, которую я позже преобразовал в маленькие функции, и что сначала было массивом, потом указателем, и это одна точка, где, если вы забудете изменить sizeof, вы ввернете ее, и это легко не увидеть один из тех.
Какауэте Фрито

3
@hyde Также то, что я знаю разницу, не означает, что все знают разницу, и почему бы не использовать то, что в основном удаляет 100% этих ошибок? Эта ошибка почти превратилась в Linux; он прибыл к Линусу, что означает, что он прошел тщательную проверку, а также означает, что есть вероятность того, что та же самая ошибка превратила его в Linux в другой части ядра, как он говорит.
Какауэте Фрито

11

«Вы ввели тонкий способ выстрелить себе в ногу»

C 'родные' массивы не сохраняют свой размер. Поэтому рекомендуется сохранять длину массива в отдельной переменной / const и передавать ее всякий раз, когда вы передаете массив, то есть:

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

Вы ДОЛЖНЫ всегда избегать нативных массивов (если вы не можете, в этом случае, следите за своей ногой). Если вы пишете на C ++, используйте контейнер STL 'vector'. «По сравнению с массивами они обеспечивают почти одинаковую производительность», и они гораздо полезнее!

// vector is a template, the <int> means it is a vector of ints
vector<int> numbers;  

// push_back() puts a new value at the end (or back) of the vector
for (int i = 0; i < 10; i++)
    numbers.push_back(i);

// Determine the size of the array
cout << numbers.size();

Смотрите: http://www.cplusplus.com/reference/stl/vector/


Я читал, что правильный способ объявить целочисленные константы в C - это использовать enumобъявление.
Раффи Хачадурян

Вопрос о C, а не C ++. Так что нет STL.
Какауэте Фрито

11
#define SIZE_OF_ARRAY(_array) (sizeof(_array) / sizeof(_array[0]))

6
Обратите внимание, что это работает только для реальных массивов, а не для указателей, которые указывают на массивы.
Дэвид Шварц

5

Если вы действительно хотите сделать это, чтобы обойти массив, я предлагаю реализовать структуру для хранения указателя на тип, для которого вы хотите массив, и целого числа, представляющего размер массива. Затем вы можете передать это своим функциям. Просто назначьте этому указателю значение переменной массива (указатель на первый элемент). Затем вы можете перейти Array.arr[i]к получению i-го элемента и использовать Array.sizeдля получения количества элементов в массиве.

Я включил некоторый код для вас. Это не очень полезно, но вы можете расширить его с помощью дополнительных функций. Если честно, если вы хотите именно этого, вам следует прекратить использовать C и использовать другой язык со встроенными функциями.

/* Absolutely no one should use this...
   By the time you're done implementing it you'll wish you just passed around
   an array and size to your functions */
/* This is a static implementation. You can get a dynamic implementation and 
   cut out the array in main by using the stdlib memory allocation methods,
   but it will work much slower since it will store your array on the heap */

#include <stdio.h>
#include <string.h>
/*
#include "MyTypeArray.h"
*/
/* MyTypeArray.h 
#ifndef MYTYPE_ARRAY
#define MYTYPE_ARRAY
*/
typedef struct MyType
{
   int age;
   char name[20];
} MyType;
typedef struct MyTypeArray
{
   int size;
   MyType *arr;
} MyTypeArray;

MyType new_MyType(int age, char *name);
MyTypeArray newMyTypeArray(int size, MyType *first);
/*
#endif
End MyTypeArray.h */

/* MyTypeArray.c */
MyType new_MyType(int age, char *name)
{
   MyType d;
   d.age = age;
   strcpy(d.name, name);
   return d;
}

MyTypeArray new_MyTypeArray(int size, MyType *first)
{
   MyTypeArray d;
   d.size = size;
   d.arr = first;
   return d;
}
/* End MyTypeArray.c */


void print_MyType_names(MyTypeArray d)
{
   int i;
   for (i = 0; i < d.size; i++)
   {
      printf("Name: %s, Age: %d\n", d.arr[i].name, d.arr[i].age);
   }
}

int main()
{
   /* First create an array on the stack to store our elements in.
      Note we could create an empty array with a size instead and
      set the elements later. */
   MyType arr[] = {new_MyType(10, "Sam"), new_MyType(3, "Baxter")};
   /* Now create a "MyTypeArray" which will use the array we just
      created internally. Really it will just store the value of the pointer
      "arr". Here we are manually setting the size. You can use the sizeof
      trick here instead if you're sure it will work with your compiler. */
   MyTypeArray array = new_MyTypeArray(2, arr);
   /* MyTypeArray array = new_MyTypeArray(sizeof(arr)/sizeof(arr[0]), arr); */
   print_MyType_names(array);
   return 0;
}

1
Невозможно обновить код strcpy(d.name, name);без обработки переполнения.
chux - Восстановить Монику

4

Лучше всего сохранить эту информацию, например, в структуре:

typedef struct {
     int *array;
     int elements;
} list_s;

Реализуйте все необходимые функции, такие как создание, уничтожение, проверка равенства и все остальное, что вам нужно. Проще передать в качестве параметра.


6
Есть ли причина для int elementsпротив size_t elements?
chux - Восстановить Монику

3

Функция sizeofвозвращает количество байтов, которое используется вашим массивом в памяти. Если вы хотите вычислить количество элементов в вашем массиве, вы должны разделить это число на sizeofтип переменной массива. Допустим int array[10];, если на вашем компьютере переменная типа integer 32-битная (или 4-байтовая), для того, чтобы получить размер вашего массива, вы должны сделать следующее:

int array[10];
int sizeOfArray = sizeof(array)/sizeof(int);

2

Вы можете использовать &оператор. Вот исходный код:

#include<stdio.h>
#include<stdlib.h>
int main(){

    int a[10];

    int *p; 

    printf("%p\n", (void *)a); 
    printf("%p\n", (void *)(&a+1));
    printf("---- diff----\n");
    printf("%zu\n", sizeof(a[0]));
    printf("The size of array a is %zu\n", ((char *)(&a+1)-(char *)a)/(sizeof(a[0])));


    return 0;
};

Вот пример вывода

1549216672
1549216712
---- diff----
4
The size of array a is 10

7
Я не понизил голос, но это все равно, что бить гвоздем кирпичом, потому что рядом с тобой не было молотка. Кроме того, люди, как правило, недовольны использованием неинициализированных переменных ... но здесь, я думаю, это служит вашей цели достаточно хорошо.
Дмитрий

2
@Dmitri здесь не доступны неинициализированные переменные
ММ,

1
Хммм. Вычитание указателя приводит к ptrdiff_t. sizeof()результаты в size_t. С не определяет, кто шире или выше / того же ранга. Таким образом, тип фактора ((char *)(&a+1)-(char *)a)/(sizeof(a[0]))не обязательно size_tи, следовательно, печать с zможет привести к UB. Простого использования printf("The size of array a is %zu\n", sizeof a/sizeof a[0]);достаточно.
chux - Восстановить Монику

1
(char *)(&a+1)-(char *)aне является константой и может быть рассчитана во время выполнения, даже с фиксированным размером a[10]. sizeof(a)/sizeof(a[0])в этом случае выполняется постоянно во время компиляции.
chux - Восстановить Монику

1

Самый простой ответ:

#include <stdio.h>

int main(void) {

    int a[] = {2,3,4,5,4,5,6,78,9,91,435,4,5,76,7,34};//for Example only
    int size;

    size = sizeof(a)/sizeof(a[0]);//Method

    printf ("size = %d",size);
    return 0;
}


0

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

sizeof(a) / sizeof (a[0])

Если aэто либо массив char, unsigned charлибо signed charвам не нужно использовать sizeofдважды, так как sizeofвыражение с одним операндом этих типов всегда приводит к 1.

Цитата из C18,6.5.3.4 / 4:

« Когда sizeofприменяется к операнду, который имеет тип char, unsigned charили signed char(или его квалифицированную версию), результат будет 1».

Таким образом, sizeof(a) / sizeof (a[0])было бы эквивалентно, NUMBER OF ARRAY ELEMENTS / 1если aэто массив типа char, unsigned charили signed char. Деление на 1 является излишним.

В этом случае вы можете просто сократить и сделать:

sizeof(a)

Например:

char a[10];
size_t length = sizeof(a);

Если вы хотите доказательства, вот ссылка на GodBolt .


Тем не менее, подразделение поддерживает безопасность, если тип значительно изменяется (хотя эти случаи редки).


1
Вы, вероятно, предпочитаете по-прежнему применять макрос с разделением, потому что тип может измениться в будущем (хотя, возможно, и маловероятно), и разделение известно во время компиляции, поэтому компилятор его оптимизирует (если он не будет изменен) твой компилятор).
Какауэте Фрито

1
@CacahueteFrito Да, я тоже об этом думал. Я принял это как примечание к ответу. Спасибо.
RobertS поддерживает Монику Челлио

-1

Примечание: это может дать вам неопределенное поведение, как указано ММ в комментарии.

int a[10];
int size = (*(&a+1)-a) ;

Для более подробной информации смотрите здесь, а также здесь .


2
Это технически неопределенное поведение; *оператор не может быть применен к пришедшим к конечному указателю
MM

3
«неопределенное поведение» означает, что стандарт C не определяет поведение. Если вы попробуете это в своей программе, тогда может произойти все что угодно
MM

@MM вы говорите *(&a+1) - a;, отличается от (&a)[1] - a;выше, не так *(&a+1)и (&a)[1]считаются 1 в конце прошлого?
QuentinUK

@QuentinUK ваши два выражения одинаковы, x[y]определяется как*(x + (y))
ММ

@ ММ, я так и думал. Но другой ответ Арджуна Шридхарана имеет 38 стрелок вверх, а это -1. И в ответе Арджуна Шридхарана не упоминается неопределенное поведение.
QuentinUK
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.