2010-10-31 5 views
13

OK, je ne suis pas entièrement un débutant, mais je ne peux pas dire que je comprends la macro suivante. La partie la plus confuse est la division avec la valeur jeté à size_t: que diable cela accomplit-il? Surtout, puisque je vois un opérateur de négation, qui, autant que je sache, pourrait aboutir à une valeur nulle. Cela ne signifie-t-il pas que cela peut conduire à une division par zéro? (Soit dit en passant, la macro est correcte et fonctionne à merveille.)Macro ARRAYSIZE C++: comment ça marche?

#define ARRAYSIZE(a) \ 
    ((sizeof(a)/sizeof(*(a)))/\ 
    static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) 
+3

est-ce la chose? À quoi sert # endif? –

Répondre

12

La première partie (sizeof(a)/sizeof(*(a))) est assez simple; il divise la taille du tableau entier (en supposant que vous passez la macro un objet de type tableau, et non un pointeur), par la taille du premier élément. Cela donne le nombre d'éléments dans le tableau.

La deuxième partie n'est pas si simple. Je pense que la division potentielle par zéro est intentionnelle; cela conduira à une erreur de compilation si, pour une raison quelconque, la taille du tableau n'est pas un multiple entier de l'un de ses éléments. En d'autres termes, il s'agit d'une sorte de contrôle de cohérence au moment de la compilation.

Cependant, je ne vois pas dans quelles circonstances cela pourrait se produire ... Comme les gens ont suggéré dans les commentaires ci-dessous, il va attraper une mauvaise utilisation (comme l'utilisation ARRAYSIZE() sur un pointeur). Cependant, il n'attrapera pas toutes les erreurs de ce genre.

+0

Je pense qu'il est là pour attraper une mauvaise utilisation occasionnelle de la macro, comme l'appeler sur un pointeur plutôt que sur un tableau. Malheureusement, il n'attrape pas des choses comme l'appeler sur un pointeur vers un objet de la même taille que le pointeur. –

+1

La circonstance principale que je peux penser est quand 'a' est un pointeur, à quelque chose dont la taille n'est pas un facteur de' sizeof (a) '. Donc, la plupart des classes, vous vous en doutez, mais vous n'obtiendrez pas la division-zéro désirée si 'a' est un' char * '. Il existe de meilleurs moyens d'écrire une aide ARRAYSIZE en C++ afin d'obtenir un avertissement/erreur quand il est utilisé avec un pointeur. Et ce n'est que C++, pas C++ et C à double usage, à cause du 'static_cast'. –

1

Cela entraîne une erreur de division par zéro (intentionnellement). La façon dont fonctionne cette macro est de diviser la taille du tableau en octets par la taille d'un seul élément de tableau en octets. Donc, si vous avez un tableau de valeurs int, où int est 4 octets (sur la plupart des machines 32 bits), un tableau de 4 valeurs serait de 16 octets. Par conséquent, lorsque vous appelez cette macro sur un tel tableau, elle exécute . Et puisque 16/4 = 4, il retourne qu'il y a 4 éléments dans le tableau.

Remarque: *array déréférence le premier élément du tableau et est équivalent à array[0]. La seconde division fait modulo-division (obtient le reste de la division), et comme toute valeur non nulle est considérée comme "vraie", l'utilisation de l'opérateur ! entraînerait une division par zéro si le reste de la division est non nul (et de même, division par 1 autrement).

3

supposons que nous avons

T arr[42]; 

ARRAYSIZE(arr) étendra à (à peu prés)

sizeof (arr)/sizeof(*arr)/!(sizeof(arr) % sizeof(*arr)) 

qui dans ce cas donne 42 /! 0 qui est 42

Si pour une matrice de sizeof raison n'est pas divisible par sizeof son élément, la division par zéro se produira. Quand cela peut-il arriver? Par exemple lorsque vous passez un tableau alloué dynamiquement au lieu d'un tableau statique!

6

La division à la fin semble être une tentative à la détection d'un argument non-tableau (par exemple un pointeur).

Il ne parvient pas à détecter cela pour, par exemple, char*, mais fonctionnerait pour T*sizeof(T) est supérieure à la taille d'un pointeur.

En C++, on préfère généralement le modèle de fonction suivante:

typedef ptrdiff_t Size; 

template< class Type, Size n > 
Size countOf(Type (&)[n]) { return n; } 

Ce modèle de fonction ne peut pas être instancié avec l'argument pointeur, seul tableau. En C++ 11, il peut alternativement être exprimé en termes de std::begin et std::end, ce qui lui permet automatiquement de fonctionner aussi pour les conteneurs standards avec des itérateurs à accès aléatoire.

Limitations: ne fonctionne pas pour un tableau de type local en C++ 03 et ne génère pas de taille de temps de compilation.

Pour la taille, vous pouvez compilation au lieu de faire comme

template< Size n > struct Sizer { char elems[n]; }; 

template< class Type, size n > 
Sizer<n> countOf_(Type (&)[n]); 

#define COUNT_OF(a) sizeof(countOf_(a).elems) 

Attention: tout le code intact par les mains du compilateur.

Mais en général, utilisez simplement le premier modèle de fonction, countOf.

Salutations & hth.

0

Le div-by-zero peut essayer d'intercepter des erreurs d'alignement causées par une raison quelconque. Comme si, avec certains paramètres du compilateur, la taille d'un élément de tableau était de 3 mais le compilateur l'arrondirait à 4 pour un accès plus rapide au tableau, alors un tableau de 4 entrées aurait la taille de 16 et! (16/3) irait à zéro, donnant la division par zéro à la compilation. Pourtant, je ne connais aucun compilateur faisant comme cela, et il peut être contre la spécification de C++ pour sizeof pour renvoyer une taille qui diffère de la taille de ce type dans un tableau ..

-1

fête ici ...

de Google C++ a mon humble avis codebase la de mise en œuvre de C++ définitive du arraysize() macro, qui comprend plusieurs rides qui ne sont pas considérés ici.

Je ne peux pas améliorer the source, qui a des commentaires clairs et complets.

1

J'ai écrit cette version de cette macro. Considérez l'ancienne version:

#include <sys/stat.h> 
#define ARRAYSIZE(a) (sizeof(a)/sizeof(*(a))) 

int main(int argc, char *argv[]) { 
    struct stat stats[32]; 
    std::cout << "sizeof stats = " << (sizeof stats) << "\n"; 
    std::cout << "sizeof *stats = " << (sizeof *stats) << "\n"; 
    std::cout << "ARRAYSIZE=" << ARRAYSIZE(stats) << "\n"; 

    foo(stats); 
} 

void foo(struct stat stats[32]) { 
    std::cout << "sizeof stats = " << (sizeof stats) << "\n"; 
    std::cout << "sizeof *stats = " << (sizeof *stats) << "\n"; 
    std::cout << "ARRAYSIZE=" << ARRAYSIZE(stats) << "\n"; 
} 

Sur une machine 64 bits, ce code produit cette sortie:

sizeof stats = 4608 
sizeof *stats = 144 
ARRAYSIZE=32 
sizeof stats = 8 
sizeof *stats = 144 
ARRAYSIZE=0 

Que se passe-t-il? Comment l'ARRAYSIZE est-il passé de 32 à zéro? Eh bien, le problème est que le paramètre function est en fait un pointeur, même s'il ressemble à un tableau. Donc, à l'intérieur de foo, "sizeof (statistiques)" est de 8 octets, et "sizeof (*) statistiques" est toujours 144.

Avec la nouvelle macro:

#define ARRAYSIZE(a) \ 
    ((sizeof(a)/sizeof(*(a)))/\ 
    static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) 

Lorsque sizeof (a) n'est pas un multiple de sizeof (* (a)), le% n'est pas nul, ce qui le! inverse, puis static_cast évalue à zéro, provoquant une division à la compilation par zéro. Donc, dans la mesure du possible dans une macro, cette division étrange attrape le problème à la compilation.

PS: en C++ 17, il suffit d'utiliser std :: taille, voir http://en.cppreference.com/w/cpp/iterator/size