Як визначити розмір масиву в C?


Відповіді:


1259

Резюме:

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

Повна відповідь:

Щоб визначити розмір масиву в байтах, ви можете скористатися sizeof оператором:

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

На моєму комп’ютері ints - 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 визначаються як константа часу компіляції.
Марк Гаррісон

449
Важливо : Не припиняйте читати тут, читайте наступну відповідь! Це працює лише для масивів стека , наприклад, якщо ви використовуєте malloc () або доступ до параметра функції, вам не пощастить. Дивись нижче.
Маркус

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

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

807

Це 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якщо передано лише вказівник на елемент 1-го масиву?
Bbvarghe

16
@Bbvarghe Це тому, що вказівники в 64-бітових системах мають 8 байт (sizeof (intArray)), але інти все ще (зазвичай) мають 4 байти (sizeof (intArray [0])).
Елідеб

13
@Pacerier: Немає правильного коду - звичайне рішення - передати довжину разом із масивом як окремий аргумент.
Жан Хомінал

10
Зачекайте, значить, немає способу отримати доступ до масиву безпосередньо з вказівника та побачити його розмір? Новачок тут.
судо

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 як отримання кількості байтів в об'єкті, і розмірof (char) завжди один. Кількість бітів у байті залежить від реалізації. Редагувати: Стандартний розділ ANSI C ++ 5.3.3 Sizeof: "Оператор sizeof видає кількість байтів у представленні об'єкта його операнду. 1; результат розміру, застосованого до будь-якого іншого фундаментального типу, визначається реалізацією. "
Скізз

Розділ 1.6 Модель пам'яті C ++: "Основним блоком зберігання в моделі пам'яті C ++ є байт. Байт, принаймні, достатньо великий, щоб містити будь-який член основного набору символів виконання, і складається з суцільної послідовності бітів, числа з яких визначено реалізацією ".
Скізз

2
Я пам'ятаю, що КРЕЙ мав C з char32 бітами. Все стандартне говорить про те, що цілі значення від 0 до 127 можуть бути представлені, а його діапазон принаймні або від -127 до 127 (знак підписано), або від 0 до 255 (знак не підписаний).
vonbrand

5
Це відмінна відповідь. Хочу прокоментувати, що всі вищезазначені твердження оцінюються як ІСТИНА.
Джавад

49

Розмір "трюку" - це найкращий спосіб, який я знаю, з однією невеликою, але (на мене, це головним вихованцем) важливою зміною використання дужок.

Як зрозуміло запис у Вікіпедії, C - 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);. Тут дужки є абсолютно абсолютно естетичними.
quetzalcoatl

@Aidiakapi Це неправда, вважайте, VLA V99.
розмотайте

@unwind Спасибі, я виправлений. Щоб виправити мій коментар, sizeofзавжди буде постійним у C ++ та C89. Масиви змінної довжини C99 можуть бути оцінені під час виконання.
Айдіакапі

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 біт у цих системах, можна навіть зустріти таку помилку. Взагалі, не хвилюйтеся з цього приводу.
Айдіакапі

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[]декларація failes як (&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для noofelevs. 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]);

Це дійсно не має великого відношення до розміру масивів явно. Я щойно помітив чимало помилок, коли насправді не спостерігав, як працює препроцесор C. Ви завжди обгортаєте параметр макросу, а не може бути залучений вираз у.


Це вірно; мій приклад був поганий. Але саме це має відбутися. Як я вже згадував, ви p + 1отримаєте тип вказівника і визнаєте недійсним весь макрос (як якщо б ви намагалися використовувати макрос у функції з параметром вказівника).

Зрештою, у цьому конкретному випадку помилка насправді не має значення (тому я просто витрачаю час усім; huzzah!), Тому що у вас немає виразів з типом "масиву". Але насправді сенс щодо підпроцесів оцінки препроцесорів, я думаю, є важливим.


2
Дякую за пояснення. Оригінальна версія призводить до помилки часу компіляції. Кланг повідомляє, що "підписане значення не є масивом, вказівником чи вектором". Це здається кращою поведінкою в цьому випадку, хоча ваші коментарі щодо порядку оцінювання в макросах добре прийняті.
Марк Гаррісон

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",        " " },
};

Але ці константи можна оцінити і в час компіляції за розміромofof :

#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])

тощо, ad infinitum.


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, неподписаний char або підписаний 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) дає розмірof всіх елементів, присутніх у масиві, sizeof (a [0]) дає розмірof 1st елементів. Припустимо, а = {1,2,3,4,5}; sizeof (a) = 20 байт (якщо sizeof (int) = 4 байти помножити на 5), sizeof (a [0]) = 4 байти, тому 20/4 = 5, тобто елементів немає
Yogeesh HT

2
@YogeeshHT Для дуже великих масивів, таких як char a[INT_MAX + 1u];, int nяк використовується в int n = sizeof (a) / sizeof (a[0]);недостатній (це UB). Використання 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)(визначено нижче) ІС потрібен, як -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()в <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 Я знаю різницю між вказівниками та масивами. Але були випадки, коли у мене була функція, яку я пізніше перетворив на маленькі функції, і те, що спочатку було масивом, пізніше - вказівником, і це один момент, коли якщо ти забудеш змінити розмір, ви його накрутите, і це легко не побачити одна з таких.
Cacahuete Frito

3
@hyde Також те, що я знаю, різниця не означає, що всі знають різницю, і чому б не використовувати щось, що в основному видаляє 100% цих помилок? Ця помилка майже перетворилася на Linux; він прибув до Linus, що означає, що він пройшов багато ретельного контролю, а це також означає, що така ж помилка зробила його в Linux в іншій частині ядра, як він каже.
Cacahuete Frito

11

"ви ввели тонкий спосіб стріляти себе в ногу"

C "рідні" масиви не зберігають їх розміри. Тому рекомендується зберігати довжину масиву в окремій змінній / const і передавати її щоразу, коли ви передаєте масив, тобто:

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

Ви завжди повинні уникати рідних масивів (якщо ви не можете, в такому випадку, пам'ятайте про ступні). Якщо ви пишете C ++, використовуйте контейнер STL 'вектор'. "У порівнянні з масивами вони забезпечують майже однакову продуктивність", і вони набагато корисніші!

// 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.
Cacahuete Frito

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];, якщо ціле число змінної типу на вашому комп’ютері становить 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 тут немає доступу до неініціалізованих змінних
MM

1
Хммм. Віднімання вказівника призводить до ptrdiff_t. sizeof()результати в size_t. C не визначає, який ширший чи вищий / той самий ранг. Тож тип коефіцієнта ((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
Ви, мабуть, вважаєте за краще все-таки застосувати макрос до поділу, тому що тип може змінитися в майбутньому (хоча, можливо, малоймовірно), і поділ відомий під час компіляції, тому компілятор оптимізує його (якщо він не змінить ваш компілятор).
Cacahuete Frito

1
@CacahueteFrito Так, я теж думав про це тим часом. Я прийняв це як бічну записку до відповіді. Дякую.
RobertS підтримує Моніку Селліо

-1

Примітка. Це може надати вам не визначену поведінку, як наголошує М.М. у коментарі.

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

Детальніше дивіться тут, а також тут .


2
Це технічно невизначена поведінка; *оператор не може бути застосований до людей до кінцевого вказівником
MM

3
"невизначена поведінка" означає, що Стандарт С не визначає поведінку. Якщо ви спробуєте це у своїй програмі, то все може статися
ММ

@MM, ти кажеш *(&a+1) - a;, відрізняється від (&a)[1] - a;вище, чи не обоє, *(&a+1)і (&a)[1]рахувати як один минулий кінець?
QuentinUK

@QuentinUK обидва вирази обидва однакові, x[y]визначається як*(x + (y))
MM

@MM Я так думав. Але в іншій відповіді Арджуна Средхарана є 38 стрілок вгору, і це -1. І у відповіді Арджуна Средхарана немає жодної згадки про невизначену поведінку.
QuentinUK
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.