Comment déterminer la taille de mon tableau en C?


Réponses:


1262

Résumé:

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

Réponse complète:

Pour déterminer la taille de votre tableau en octets, vous pouvez utiliser l' sizeof opérateur:

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

Sur mon ordinateur, les entiers font 4 octets, donc n est 68.

Pour déterminer le nombre d'éléments dans le tableau, nous pouvons diviser la taille totale du tableau par la taille de l'élément du tableau. Vous pouvez le faire avec le type, comme ceci:

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

et obtenez la bonne réponse (68/4 = 17), mais si le type de achangement vous aurait un méchant bug si vous oubliez de le changer sizeof(int)aussi.

Ainsi, le diviseur préféré est sizeof(a[0])ou l'équivalent sizeof(*a), la taille du premier élément du tableau.

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

Un autre avantage est que vous pouvez désormais facilement paramétrer le nom du tableau dans une macro et obtenir:

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

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

6
Le code généré sera identique, car le compilateur connaît le type de * int_arr au moment de la compilation (et donc la valeur de sizeof (* int_arr)). Ce sera une constante, et le compilateur peut optimiser en conséquence.
Mark Harrison

10
Cela devrait être le cas avec tous les compilateurs, car les résultats de sizeof sont définis comme une constante de temps de compilation.
Mark Harrison

451
Important : n'arrêtez pas de lire ici, lisez la réponse suivante! Cela ne fonctionne que pour les tableaux de la pile , par exemple si vous utilisez malloc () ou accédez à un paramètre de fonction, vous n'avez pas de chance. Voir ci-dessous.
Markus

7
Pour la programmation de l'API Windows en C ou C ++, il y a le ARRAYSIZEmakro défini dans WinNT.h(qui est tiré par d'autres en-têtes). Les utilisateurs de WinAPI n'ont donc pas besoin de définir leur propre makro.
Lumi

17
@Markus cela fonctionne pour n'importe quelle variable qui a un type de tableau; cela ne doit pas être "sur la pile". Par exemple static int a[20];. Mais votre commentaire est utile aux lecteurs qui ne réalisent pas la différence entre un tableau et un pointeur.
MM

808

La sizeofmanière est la bonne, si vous traitez des tableaux non reçus comme paramètres. Un tableau envoyé en tant que paramètre à une fonction est traité comme un pointeur, il sizeofretournera donc la taille du pointeur au lieu de celle du tableau.

Ainsi, à l'intérieur des fonctions, cette méthode ne fonctionne pas. Au lieu de cela, passez toujours un paramètre supplémentaire size_t sizeindiquant le nombre d'éléments dans le tableau.

Tester:

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

Sortie (dans un système d'exploitation Linux 64 bits):

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

Sortie (dans un système d'exploitation Windows 32 bits):

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

10
pourquoi length of parameter:2si seulement un pointeur vers le 1er élément du tableau est passé?
Bbvarghe

16
@Bbvarghe C'est parce que les pointeurs dans les systèmes 64 bits font 8 octets (sizeof (intArray)), mais les entiers ont toujours (généralement) 4 octets de long (sizeof (intArray [0])).
Elideb

13
@Pacerier: Il n'y a pas de code correct - la solution habituelle est de passer la longueur avec le tableau comme argument séparé.
Jean Hominal

10
Attendez, donc il n'y a aucun moyen d'accéder directement au tableau à partir d'un pointeur et de voir sa taille? Nouveau sur C ici.
sudo

7
Trouw @ Michael: vous pouvez utiliser la syntaxe de l' opérateur si est fait vous sentir mieux: (sizeof array / sizeof *array).
chqrlie du

134

Il convient de noter que cela sizeofn'aide pas lorsqu'il s'agit d'une valeur de tableau qui s'est désintégrée en un pointeur: même si elle pointe vers le début d'un tableau, pour le compilateur, c'est la même chose qu'un pointeur vers un seul élément de ce tableau . Un pointeur ne "se souvient" de rien d'autre du tableau qui a été utilisé pour l'initialiser.

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: La norme définit sizeof comme donnant le nombre d'octets dans l'objet et que sizeof (char) est toujours un. Le nombre de bits dans un octet est spécifique à l'implémentation. Edit: ANSI C ++ standard section 5.3.3 Sizeof: "L'opérateur sizeof donne le nombre d'octets dans la représentation d'objet de son opérande. [...] sizeof (char), sizeof (char signé) et sizeof (char non signé) sont 1; le résultat de sizeof appliqué à tout autre type fondamental est défini par l'implémentation. "
Skizz

Section 1.6 Le modèle de mémoire C ++: "L'unité de stockage fondamentale dans le modèle de mémoire C ++ est l'octet. Un octet est au moins assez grand pour contenir n'importe quel membre du jeu de caractères d'exécution de base et est composé d'une séquence contiguë de bits, le nombre dont la mise en œuvre est définie. "
Skizz

2
Je me souviens que le CRAY avait C avec char32 bits. Tout ce que la norme dit, c'est que les valeurs entières de 0 à 127 peuvent être représentées, et sa plage est au moins de -127 à 127 (le caractère est signé) ou de 0 à 255 (le caractère est non signé).
vonbrand

5
C'est une excellente réponse. Je tiens à dire que toutes les affirmations ci-dessus sont évaluées à VRAI.
Javad

49

La taille du "truc" est le meilleur moyen que je connaisse, avec un petit mais (pour moi, ce qui est une bête noire majeure) un changement important dans l'utilisation des parenthèses.

Comme l'indique clairement l'entrée Wikipedia, les C ne sizeofsont pas une fonction; c'est un opérateur . Ainsi, il ne nécessite pas de parenthèses autour de son argument, sauf si l'argument est un nom de type. Ceci est facile à retenir, car il fait ressembler l'argument à une expression cast, qui utilise également des parenthèses.

Donc: si vous avez les éléments suivants:

int myArray[10];

Vous pouvez trouver le nombre d'éléments avec du code comme celui-ci:

size_t n = sizeof myArray / sizeof *myArray;

Pour moi, cela se lit beaucoup plus facilement que l'alternative avec parenthèses. Je suis également favorable à l'utilisation de l'astérisque dans la partie droite de la division, car il est plus concis que l'indexation.

Bien sûr, tout cela est également au moment de la compilation, il n'est donc pas nécessaire de s'inquiéter de la division affectant les performances du programme. Utilisez donc ce formulaire partout où vous le pouvez.

Il est toujours préférable d'utiliser sizeof sur un objet réel lorsque vous en avez un, plutôt que sur un type, car vous n'avez alors pas à vous soucier de faire une erreur et d'indiquer le mauvais type.

Par exemple, supposons que vous ayez une fonction qui génère des données sous forme de flux d'octets, par exemple sur un réseau. Appelons la fonction send()et faisons en sorte qu'elle prenne comme arguments un pointeur vers l'objet à envoyer et le nombre d'octets dans l'objet. Ainsi, le prototype devient:

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

Et puis vous devez envoyer un entier, donc vous le codez comme ceci:

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

Maintenant, vous avez introduit une manière subtile de vous tirer une balle dans le pied, en spécifiant le type de fooà deux endroits. Si l'un change mais pas l'autre, le code se casse. Ainsi, faites-le toujours comme ceci:

send(&foo, sizeof foo);

Maintenant tu es protégé. Bien sûr, vous dupliquez le nom de la variable, mais cela a une forte probabilité de se casser d'une manière que le compilateur peut détecter, si vous la changez.


Au fait, sont-ils des instructions identiques au niveau du processeur? Nécessite sizeof(int)moins d'instructions que sizeof(foo)?
Pacerier

@Pacerier: non, ils sont identiques. Pensez à int x = 1+1;versus int x = (1+1);. Ici, les parenthèses sont purement absolument juste esthétiques.
quetzalcoatl

@Aidiakapi Ce n'est pas vrai, considérez les VLA C99.
détendre le

@unwind Merci, je me tiens corrigé. Pour corriger mon commentaire, sizeofsera toujours constant en C ++ et C89. Avec les tableaux à longueur variable de C99, il peut être évalué au moment de l'exécution.
Aidiakapi

2
sizeofpeut être un opérateur mais il doit être traité comme une fonction selon Linus Torvalds. Je suis d'accord. Lisez son rationnel ici: lkml.org/lkml/2012/7/11/103
Dr. Person Person II

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

Consultez ce lien pour des explications


7
Petite piqûre: le résultat de la soustraction du pointeur est de type ptrdiff_t. (Généralement sur un système 64 bits, ce sera un type plus grand que int). Même si vous passez intà ptrdiff_tdans ce code, il a toujours un bogue s'il arroccupe plus de la moitié de l'espace d'adressage.
MM

2
@MM Un autre petit problème: en fonction de l'architecture de votre système, l'espace d'adressage n'est pas aussi grand que la taille du pointeur sur la plupart des systèmes. Windows, par exemple, limite l'espace d'adressage pour les applications 64 bits à 8 To ou 44 bits. Ainsi, même si vous avez un tableau supérieur à la moitié de votre espace d'adressage de 4,1 To par exemple, ce ne sera pas un bug. Seulement si votre espace d'adressage dépasse 63 bits sur ces systèmes, il est même possible de rencontrer un tel bogue. En général, ne vous en faites pas.
Aidiakapi du

1
@Aidiakapi sur Linux x86 32 bits ou sur Windows avec l' /3Goption vous avez un partage utilisateur / noyau 3G / 1G, ce qui vous permet d'avoir une taille de tableau pouvant atteindre 75% de la taille de l'espace d'adressage.
Ruslan

1
Considérez foo buf1[80]; foo buf2[sizeof buf1/sizeof buf1[0]]; foo buf3[(&buf1)[1] - buf1];comme variables globales. buf3[]échoue car ce (&buf1)[1] - buf1n'est pas une constante.
chux

2
Il s'agit d'un comportement techniquement indéfini car la norme interdit explicitement le déréférencement au-delà de la fin d'un tableau (même si vous n'essayez pas de lire la valeur stockée)
MM


21

Si vous connaissez le type de données du tableau, vous pouvez utiliser quelque chose comme:

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

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

Ou si vous ne connaissez pas le type de données du tableau, vous pouvez utiliser quelque chose comme:

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

Remarque: Cette chose ne fonctionne que si le tableau n'est pas défini au moment de l'exécution (comme malloc) et que le tableau n'est pas passé dans une fonction. Dans les deux cas, arr(nom du tableau) est un pointeur.


4
int noofele = sizeof(arr)/sizeof(int);n'est qu'à mi-chemin meilleur que le codage int noofele = 9;. L'utilisation sizeof(arr)maintient la flexibilité en cas de modification de la taille du tableau. Encore sizeof(int)faut-il une mise à jour en cas de arr[]changement de type . Mieux vaut l'utiliser sizeof(arr)/sizeof(arr[0])même si le type est bien connu. On ne sait pas pourquoi utiliser intpour noofelevs size_t, le type renvoyé par sizeof().
chux

19

La macro ARRAYELEMENTCOUNT(x)que tout le monde utilise n'évalue pas correctement . En réalité, ceci est juste une question sensible, car vous ne pouvez pas avoir d'expressions qui aboutissent à un type «tableau».

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

ARRAYELEMENTCOUNT(p + 1);

Évalue en fait:

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

Tandis que

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

ARRAYELEMENTCOUNT(p + 1);

Il évalue correctement:

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

Cela n'a vraiment pas grand-chose à voir avec la taille des tableaux explicitement. Je viens de remarquer beaucoup d'erreurs de ne pas vraiment observer le fonctionnement du préprocesseur C. Vous encapsulez toujours le paramètre de macro dans lequel aucune expression ne peut être impliquée.


C'est correct; mon exemple était mauvais. Mais c'est exactement ce qui devrait arriver. Comme je l'ai mentionné précédemment, cela p + 1se terminera comme un type de pointeur et invalidera la macro entière (comme si vous tentiez d'utiliser la macro dans une fonction avec un paramètre de pointeur).

À la fin de la journée, dans ce cas particulier , la faute n'a pas vraiment d'importance (donc je perds juste le temps de tout le monde; huzzah!), Parce que vous n'avez pas d'expressions avec un type de 'tableau'. Mais vraiment, le point sur les subtils d'évaluation du préprocesseur est, je pense, important.


2
Merci pour l'explication. La version d'origine entraîne une erreur de compilation. Clang rapporte que "la valeur en indice n'est pas un tableau, un pointeur ou un vecteur". Cela semble préférable dans ce cas, bien que vos commentaires sur l'ordre d'évaluation dans les macros soient bien pris en compte.
Mark Harrison

1
Je n'avais pas pensé à la plainte du compilateur comme une notification automatique d'un type incorrect. Merci!

3
Y a-t-il une raison de ne pas l'utiliser (sizeof (x) / sizeof (*x))?
seriousdev

16

Pour les tableaux multidimensionnels, c'est un peu plus compliqué. Souvent, les gens définissent des constantes macro explicites, c.-à-d.

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

Mais ces constantes peuvent également être évaluées au moment de la compilation avec 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);

Notez que ce code fonctionne en C et C ++. Pour les tableaux de plus de deux dimensions, utilisez

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

etc., à l'infini.


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

En fonction du type arraya, vous n'avez pas besoin d'utiliser sizeof(array) / sizeof(array[0])si arrayest un tableau de deux char, unsigned charou signed char- Citation de C18,6.5.3.4 / 4: « Lorsque sizeof est appliqué à un opérande qui est de type char, unsigned char, ou char signé , (ou une version qualifiée de celui-ci) le résultat est 1. " Dans ce cas, vous pouvez simplement faire sizeof(array)comme expliqué dans ma réponse dédiée .
RobertS soutient Monica Cellio

15

Taille d'un tableau en 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
Curieux que le code soit size_t size_of_elementencore utilisé intavec int n = sizeof (a) / sizeof (a[0]); et nonsize_t n = sizeof (a) / sizeof (a[0]);
chux - Réintègre Monica

1
Salut @Yogeesh HT, pouvez-vous s'il vous plaît répondre au doute de chux. Je suis également très curieux de savoir comment int n = sizeof (a) / sizeof (a [0]) donne la longueur du tableau et pourquoi nous n'utilisons pas size_t pour la longueur du tableau. Quelqu'un peut-il y répondre?
Brain

1
@Brain sizeof (a) donne la taille de tous les éléments présents dans le tableau une tailleof (a [0]) donne la taille du 1er élément. Supposons a = {1,2,3,4,5}; sizeof (a) = 20 octets (si sizeof (int) = 4 octets multiplier 5), sizeof (a [0]) = 4 octets, donc 20/4 = 5, c'est-à-dire aucun élément
Yogeesh HT

2
@YogeeshHT Pour les très grands tableaux comme char a[INT_MAX + 1u];, int ntel qu'utilisé dans int n = sizeof (a) / sizeof (a[0]);est insuffisant (c'est UB). L'utilisation size_t n = sizeof (a) / sizeof (a[0]);n'entraîne pas ce problème.
chux

13

Je conseillerais de ne jamais utiliser sizeof(même s'il peut être utilisé) pour obtenir l'une des deux tailles différentes d'un tableau, soit en nombre d'éléments soit en octets, qui sont les deux derniers cas que je montre ici. Pour chacune des deux tailles, les macros illustrées ci-dessous peuvent être utilisées pour la rendre plus sûre. La raison est de rendre évidente l'intention du code à mainteneurs, et la différence sizeof(ptr)de sizeof(arr)prime abord (qui écrit de cette façon est pas évident), de sorte que les insectes sont alors évident pour tout le monde à lire le code.


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)(défini ci-dessous) EST nécessaire en tant que -Wsizeof-pointer-divbuggy (en avril / 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__;  \
                }                                                       \
        )                                                               \
)

Il y a eu des bugs importants concernant ce sujet: https://lkml.org/lkml/2015/9/3/428

Je ne suis pas d'accord avec la solution proposée par Linus, qui consiste à ne jamais utiliser la notation matricielle pour les paramètres des fonctions.

J'aime la notation de tableau comme documentation qu'un pointeur est utilisé comme tableau. Mais cela signifie qu'une solution infaillible doit être appliquée pour qu'il soit impossible d'écrire du code bogué.

À partir d'un tableau, nous avons trois tailles que nous pourrions vouloir savoir:

  • La taille des éléments du tableau
  • Le nombre d'éléments dans le tableau
  • La taille en octets que le tableau utilise en mémoire

La taille des éléments du tableau

Le premier est très simple, et cela n'a pas d'importance si nous avons affaire à un tableau ou à un pointeur, car c'est fait de la même manière.

Exemple d'utilisation:

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

qsort() a besoin de cette valeur comme troisième argument.


Pour les deux autres tailles, qui sont le sujet de la question, nous voulons nous assurer que nous avons affaire à un tableau, et casser la compilation sinon, car si nous avons affaire à un pointeur, nous obtiendrons des valeurs erronées . Lorsque la compilation est interrompue, nous pourrons facilement voir que nous n'avons pas affaire à un tableau, mais à un pointeur à la place, et nous devrons simplement écrire le code avec une variable ou une macro qui stocke la taille du tableau derrière le pointeur.


Le nombre d'éléments dans le tableau

Celui-ci est le plus courant, et de nombreuses réponses vous ont fourni la macro typique ARRAY_SIZE:

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

Étant donné que le résultat de ARRAY_SIZE est couramment utilisé avec des variables de type signées ptrdiff_t, il est bon de définir une variante signée de cette macro:

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

Les tableaux avec plus de PTRDIFF_MAXmembres vont donner des valeurs invalides pour cette version signée de la macro, mais à la lecture de C17 :: 6.5.6.9, des tableaux comme celui-ci jouent déjà avec le feu. Uniquement ARRAY_SIZEet size_tdoit être utilisé dans ces cas.

Les versions récentes de compilateurs, tels que GCC 8, vous avertiront lorsque vous appliquerez cette macro à un pointeur, elle est donc sûre (il existe d'autres méthodes pour la sécuriser avec des compilateurs plus anciens).

Il fonctionne en divisant la taille en octets de l'ensemble du tableau par la taille de chaque élément.

Exemples d'utilisation:

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

Si ces fonctions n'utilisaient pas de tableaux, mais les obtenaient à la place comme paramètres, l'ancien code ne se compilerait pas, il serait donc impossible d'avoir un bogue (étant donné qu'une version récente du compilateur est utilisée, ou qu'une autre astuce est utilisée) , et nous devons remplacer l'appel de macro par la valeur:

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

La taille en octets que le tableau utilise en mémoire

ARRAY_SIZE est couramment utilisé comme solution au cas précédent, mais ce cas est rarement écrit en toute sécurité, peut-être parce qu'il est moins courant.

La manière courante d'obtenir cette valeur est d'utiliser sizeof(arr). Le problème: le même qu'avec le précédent; si vous avez un pointeur au lieu d'un tableau, votre programme deviendra fou.

La solution au problème consiste à utiliser la même macro que précédemment, que nous savons être sûre (elle rompt la compilation si elle est appliquée à un pointeur):

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

Son fonctionnement est très simple: il annule la division qui le ARRAY_SIZEfait, donc après des annulations mathématiques, vous vous retrouvez avec une seule sizeof(arr), mais avec la sécurité supplémentaire de la ARRAY_SIZEconstruction.

Exemple d'utilisation:

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

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

memset() a besoin de cette valeur comme troisième argument.

Comme précédemment, si le tableau est reçu en paramètre (un pointeur), il ne se compilera pas, et nous devrons remplacer l'appel de macro par la valeur:

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

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

Mise à jour (23 / avril / 2020): -Wsizeof-pointer-divest buggé :

Aujourd'hui, j'ai découvert que le nouvel avertissement dans GCC ne fonctionne que si la macro est définie dans un en-tête qui n'est pas un en-tête système. Si vous définissez la macro dans un en-tête installé sur votre système (généralement /usr/local/include/ou /usr/include/) ( #include <foo.h>), le compilateur n'émettra PAS d'avertissement (j'ai essayé GCC 9.3.0).

Nous avons donc #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))et voulons le rendre sûr. Nous aurons besoin de C11 _Static_assert()et de quelques extensions GCC: déclarations et déclarations dans les expressions , __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]);                                \
}                                                                       \
)

Maintenant, ARRAY_SIZE()c'est complètement sûr, et donc tous ses dérivés seront sûrs.


Mise à jour: libbsd fournit __arraycount():

Libbsd fournit la macro __arraycount()dans <sys/cdefs.h>, ce qui est dangereux parce qu'il manque une paire de parenthèses, mais nous pouvons ajouter ces parenthèses nous - mêmes, et donc nous ne même pas besoin d'écrire la division dans notre tête (pourquoi nous dupliquer du code qui existe déjà? ). Cette macro est définie dans un en-tête système, donc si nous l'utilisons, nous sommes obligés d'utiliser les macros ci-dessus.

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

Certains systèmes fournissent nitems()à la <sys/param.h>place et certains systèmes fournissent les deux. Vous devez vérifier votre système et utiliser celui que vous avez, et peut-être utiliser des conditions de préprocesseur pour la portabilité et prendre en charge les deux.


Mise à jour: autorisez la macro à être utilisée à la portée du fichier:

Malheureusement, l' ({})extension gcc ne peut pas être utilisée à la portée du fichier. Pour pouvoir utiliser la macro à la portée du fichier, l'assertion statique doit être à l'intérieur sizeof(struct {}). Ensuite, multipliez-le par 0pour ne pas affecter le résultat. Un cast to (int)pourrait être bon pour simuler une fonction qui retourne (int)0(dans ce cas, ce n'est pas nécessaire, mais il est réutilisable pour d'autres choses).

#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
Pourriez-vous expliquer pourquoi le downvote? Cela montre une solution à une construction dangereuse et commune ( sizeof(arr)) qui ne figurent pas ailleurs: ARRAY_BYTES(arr).
Cacahuete Frito

2
ARRAY_SIZE est suffisamment commun pour être utilisé librement, et ARRAY_BYTES est très explicite dans son nom, devrait être défini à côté de ARRAY_SIZE afin qu'un utilisateur puisse voir les deux facilement et par son utilisation, je ne pense pas que quiconque lisant le code ait des doutes sur ce Cela fait. Ce que je voulais dire, c'est de ne pas utiliser un simple sizeof, mais d'utiliser ces constructions à la place; si vous avez envie d'écrire ces constructions à chaque fois, vous allez probablement faire une erreur (très fréquent si vous copiez-collez, et aussi très fréquent si vous les écrivez à chaque fois car elles ont beaucoup de parenthèses) ...
Cacahuete Frito

3
..., donc je me tiens sur la conclusion principale: un single sizeofest clairement dangereux (les raisons sont dans la réponse), et ne pas utiliser de macros mais utiliser les constructions que j'ai fournies, à chaque fois, est encore plus dangereux, donc la seule voie à suivre est des macros.
Cacahuete Frito

3
@MarkHarrison Je connais la différence entre les pointeurs et les tableaux. Mais il y a eu des fois où j'ai eu une fonction que j'ai plus tard refactorisée en petites fonctions, et ce qui était d'abord un tableau, plus tard était un pointeur, et c'est un point où si vous oubliez de changer la taille de, vous la vissez, et il est facile de ne pas voir l'un de ces.
Cacahuete Frito

3
@hyde Aussi que je sais que la différence ne signifie pas que tout le monde connaît la différence, et pourquoi ne pas utiliser quelque chose qui supprime fondamentalement 100% de ces bugs? Ce bogue est presque arrivé à Linux; il est arrivé à Linus, ce qui signifie qu'il a passé beaucoup de contrôle, et cela signifie également qu'il est possible que le même bogue soit entré dans Linux dans une autre partie du noyau, comme il le dit.
Cacahuete Frito

11

"vous avez introduit une manière subtile de vous tirer une balle dans le pied"

Les tableaux «natifs» ne stockent pas leur taille. Il est donc recommandé d'enregistrer la longueur du tableau dans une variable / const distincte et de la transmettre chaque fois que vous passez le tableau, c'est-à-dire:

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

Vous DEVEZ toujours éviter les tableaux natifs (sauf si vous ne pouvez pas, auquel cas, faites attention à votre pied). Si vous écrivez en C ++, utilisez le conteneur 'vector' de la STL . "Par rapport aux tableaux, ils fournissent presque les mêmes performances", et ils sont bien plus utiles!

// 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();

Voir: http://www.cplusplus.com/reference/stl/vector/


J'ai lu que la bonne façon de déclarer des constantes entières en C est d'utiliser une enumdéclaration.
Raffi Khatchadourian

La question concerne C, pas C ++. Donc pas de STL.
Cacahuete Frito

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

6
Notez que cela ne fonctionne que pour les tableaux réels, pas les pointeurs qui pointent vers des tableaux.
David Schwartz

5

Si vous voulez vraiment faire cela pour faire circuler votre tableau, je suggère d'implémenter une structure pour stocker un pointeur sur le type dont vous voulez un tableau et un entier représentant la taille du tableau. Ensuite, vous pouvez transmettre cela à vos fonctions. Attribuez simplement la valeur de la variable du tableau (pointeur au premier élément) à ce pointeur. Ensuite, vous pouvez aller Array.arr[i]chercher le i-ème élément et utiliser Array.sizepour obtenir le nombre d'éléments dans le tableau.

J'ai inclus du code pour vous. Ce n'est pas très utile mais vous pouvez l'étendre avec plus de fonctionnalités. Pour être honnête, si ce sont les choses que vous voulez, vous devez cesser d'utiliser C et utiliser un autre langage avec ces fonctionnalités intégrées.

/* 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
Impossible de voter avec du code qui ne strcpy(d.name, name);gère pas le débordement.
chux

4

La meilleure façon est d'enregistrer ces informations, par exemple, dans une structure:

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

Implémentez toutes les fonctions nécessaires telles que créer, détruire, vérifier l'égalité et tout ce dont vous avez besoin. Il est plus facile de passer en paramètre.


6
Une raison pour int elementsvs size_t elements?
chux

3

La fonction sizeofrenvoie le nombre d'octets utilisé par votre tableau dans la mémoire. Si vous souhaitez calculer le nombre d'éléments dans votre tableau, vous devez diviser ce nombre par le sizeoftype de variable du tableau. Disons que int array[10];si le nombre entier de type variable de votre ordinateur est de 32 bits (ou 4 octets), pour obtenir la taille de votre tableau, vous devez procéder comme suit:

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

2

Vous pouvez utiliser l' &opérateur. Voici le code source:

#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;
};

Voici l'exemple de sortie

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

7
Je n'ai pas downvote, mais c'est comme frapper un clou avec une brique parce que vous n'avez pas remarqué un marteau à côté de vous. De plus, les gens ont tendance à froncer les sourcils en utilisant des variables non initialisées ... mais ici, je suppose que cela sert assez bien votre objectif.
Dmitri

2
@Dmitri aucune variable non initialisée n'est accessible ici
MM

1
Hmmm. La soustraction du pointeur mène à ptrdiff_t. sizeof()résulte en size_t. C ne définit pas ce qui est plus large ou plus élevé / même rang. Le type de quotient ((char *)(&a+1)-(char *)a)/(sizeof(a[0]))n'est donc pas certain size_tet donc l'impression avec zpeut conduire à UB. Une simple utilisation printf("The size of array a is %zu\n", sizeof a/sizeof a[0]);est suffisante.
chux

1
(char *)(&a+1)-(char *)an'est pas une constante et peut être calculée au moment de l'exécution, même avec une taille fixe a[10]. sizeof(a)/sizeof(a[0])est constant fait au moment de la compilation dans ce cas.
chux

1

La réponse la plus simple:

#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;
}

1

Une solution plus élégante sera

size_t size = sizeof(a) / sizeof(*a);

0

Outre les réponses déjà fournies, je voudrais souligner un cas particulier en utilisant

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

Si aest soit un tableau de char, unsigned charsoit signed charvous n'avez pas besoin de l'utiliser sizeofdeux fois car une sizeofexpression avec un opérande de ces types résulte toujours en 1.

Citation de C18,6.5.3.4 / 4:

« Quand sizeofon applique sur un opérande qui est de type char, unsigned char, ou signed char, (ou une version qualifiée de celui - ci) , le résultat est 1».

Ainsi, sizeof(a) / sizeof (a[0])serait équivalent à NUMBER OF ARRAY ELEMENTS / 1if aest un tableau de type char, unsigned charou signed char. La division par 1 est redondante.

Dans ce cas, vous pouvez simplement abréger et faire:

sizeof(a)

Par exemple:

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

Si vous voulez une preuve, voici un lien vers GodBolt .


Néanmoins, la division maintient la sécurité, si le type change de manière significative (bien que ces cas soient rares).


1
Vous préférez probablement appliquer une macro à la division, car le type peut changer à l'avenir (bien que cela soit peu probable), et la division est connue au moment de la compilation, donc le compilateur l'optimisera (s'il ne change pas, veuillez votre compilateur).
Cacahuete Frito

1
@CacahueteFrito Oui, j’y ai pensé aussi entre-temps. Je l'ai pris comme note secondaire dans la réponse. Je vous remercie.
RobertS soutient Monica Cellio le

-1

Remarque: Celui-ci peut vous donner un comportement indéfini comme indiqué par MM dans le commentaire.

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

Pour plus de détails, voir ici et aussi ici .


2
Il s'agit d'un comportement techniquement indéfini; l' *opérateur ne peut pas être appliqué à un pointeur passé la fin
MM

3
"comportement indéfini" signifie que la norme C ne définit pas le comportement. Si vous l'essayez dans votre programme, alors tout peut arriver
MM

@MM dites-vous que *(&a+1) - a;c'est différent de ce qui (&a)[1] - a;précède, n'est-ce pas les deux *(&a+1)et (&a)[1]comptez comme 1 après la fin?
QuentinUK

@QuentinUK vos deux expressions sont les mêmes, x[y]est défini comme*(x + (y))
MM

@MM Je le pensais. Mais l'autre réponse, par Arjun Sreedharan, a 38 flèches vers le haut et cela a -1. Et la réponse d'Arjun Sreedharan ne fait aucune mention d'un comportement indéfini.
QuentinUK
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.