2012-10-08 24 views
15

Cada C programador puede determinar el número de elementos en una matriz con esta macro bien conocido:determinar de manera fiable el número de elementos en una matriz

#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a]) 

Aquí es un caso típico uso:

int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19}; 
printf("%lu\n", NUM_ELEMS(numbers));   // 8, as expected 

Sin embargo, nada impide que el programador se transfieran accidentalmente un puntero en lugar de una matriz:

int * pointer = numbers; 
printf("%lu\n", NUM_ELEMS(pointer)); 

En mi sistema, esto imprime 2, porque aparentemente, un puntero es dos veces más grande que un entero. Pensé acerca de cómo prevenir el programador de pasar un puntero por error, y he encontrado una solución:

#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a])) 

Esto funciona porque un puntero a una matriz tiene el mismo valor que un puntero a su primer elemento. Si pasa un puntero en su lugar, el puntero se comparará con un puntero a sí mismo, que casi siempre es falso. (La única excepción es un puntero nulo recursiva, es decir, un puntero nulo que apunta a sí puedo vivir con eso..)

pasar accidentalmente un puntero en lugar de una matriz ahora desencadena un error en tiempo de ejecución:

Assertion `(void*)&(pointer) == (void*)(pointer)' failed. 

¡Agradable! Ahora tengo un par de preguntas:

  1. Es mi uso de assert como el operando de la izquierda de la norma válida expresión con coma C? Es decir, ¿el estándar me permite usar assert como expresión? Lo siento si esta es una pregunta tonta :)

  2. ¿Se puede realizar el control de alguna manera en tiempo de compilación?

  3. Mi compilador de C piensa que int b[NUM_ELEMS(a)]; es un VLA. ¿Alguna forma de convencerlo de lo contrario?

  4. ¿Soy el primero en pensar en esto? Si es así, ¿cuántas vírgenes puedo esperar para estar en el cielo? :)

+1

En cuanto a la parte (4), bastante seguro de que es * no * 72. Creo que t sombrero de un valor reservado para otra cosa ... –

+0

no ¿Quieres decir 'sizeof a [0]'? –

+0

Cada programador c real sabe que no perder de vista el tamaño de las matrices es su problema, no los compiladores y trucos para que "la figura hacia fuera" son de utilidad estrictamente limitada debido a que la información sólo se retiene en su alcance.Si desea que el compilador se ocupe de esto por usted, use un lenguaje más inteligente. Quiero decir, solo necesitas ir a C++ y usar 'std :: vector' o (con C++ 11)' std :: array', así que no es un gran cambio. – dmckee

Respuesta

9

Is my usage of assert as the left operand of the comma expression valid standard C? That is, does the standard allow me to use assert as an expression?

Sí, es válido como el operando izquierdo del operador de coma puede ser una expresión de tipo void.Y assert función tiene void como su tipo de retorno.

My C compiler thinks that int b[NUM_ELEMS(a)]; is a VLA. Any way to convince him otherwise?

Se cree así porque el resultado de una expresión coma nunca es una expresión constante (e..g, 1, 2 no es una expresión constante).

EDIT1: agrega la actualización a continuación.

tengo otra versión de la macro que funciona en tiempo de compilación:

#define NUM_ELEMS(arr)             \ 
(sizeof (struct {int not_an_array:((void*)&(arr) == &(arr)[0]);}) * 0 \ 
    + sizeof (arr)/sizeof (*(arr))) 

y que parece funcionar incluso también con inicializadores para un objeto con una duración de almacenamiento estático. Y también funciona correctamente con su ejemplo de int b[NUM_ELEMS(a)]

Edit2:

para hacer frente a @DanielFischer comentario. Las obras macro anterior con gccsin-pedantic sólo porque gcc Acepta:

(void *) &arr == arr 

como una expresión constante entera, mientras que considere

(void *) &ptr == ptr 

no es una expresión constante entera. Según C son ambos no integer expresiones constantes y con -pedantic, gcc emite correctamente un diagnóstico en ambos casos.

Que yo sepa, no hay manera 100% portátil para escribir este NUM_ELEM macro. C tiene reglas más flexibles con expresiones constantes inicializador (véase 6.6p7 en C99), que podrían ser explotados para escribir esta macro (por ejemplo, con sizeof y literales compuestos) pero en el bloque-scope C no requiere inicializadores ser expresiones constantes por lo que se no es posible tener una sola macro que funcione en todos los casos.

Edit3:

creo que vale la pena mencionar que el núcleo Linux tiene un ARRAY_SIZE macro (en include/linux/kernel.h) que implementa un control de este tipo cuando se ejecuta escasa (el núcleo comprobador de análisis estático).

Su solución no es portátil y hacer uso de dos extensiones de GNU:

  • typeof operador
  • __builtin_types_compatible_p función incorporada

Básicamente se ve como algo así:

#define NUM_ELEMS(arr) \ 
(sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));}) \ 
    + sizeof (arr)/sizeof (*(arr))) 
+0

¡Impresionante! Te concederé la mitad de las vírgenes, si te parece bien. – fredoverflow

+0

Desafortunadamente, con '-pedantic-errors', obtengo' error: bit-field 'not_an_array' width no es una expresión constante entera [-pedantic] 'de gcc, clang da un error por defecto :( –

+0

@DanielFischer see my second editar que resuelva su comentario – ouah

3
  1. Sí. La expresión izquierda de un operador de coma siempre se evalúa como una expresión vacía (C99 6.5.17 # 2). Desde assert() es una expresión vacía, no hay problema para empezar.
  2. Quizás. Si bien el preprocesador C no conoce los tipos y los moldes y no puede comparar las direcciones, puede usar el mismo truco que para evaluar sizeof() en tiempo de compilación, p. declarando una matriz cuya dimensión es una expresión booleana. Cuando 0 es una violación de restricción y se debe emitir un diagnóstico. Lo he intentado aquí, pero hasta ahora no he tenido éxito ... tal vez la respuesta en realidad es "no".
  3. No. Los lanzamientos (de tipos de puntero) no son expresiones constantes enteras.
  4. Probablemente no (nada nuevo bajo el sol en estos días). Un número indeterminado de vírgenes de sexo indeterminado :-)
Cuestiones relacionadas