vectores punteros programacion matriz matrices dinamica arreglos c arrays macros c-preprocessor

programacion - Macro de tamaño de matriz que rechaza los punteros



punteros y matrices en c (9)

Aquí hay otro que se basa en la extensión gcc typeof :

#define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; / sizeof(arr) / sizeof(arr[0]);})

Esto funciona al intentar configurar un objeto idéntico e inicializarlo con una matriz designada como inicializador. Si se pasa una matriz, entonces el compilador está contento. Si se pasa puntero el compilador se queja con:

arraysize.c: In function ''main'': arraysize.c:11: error: array index in non-array initializer arraysize.c:11: error: (near initialization for ''p_is_a_pointer'')

La macro estándar de tamaño de matriz que a menudo se enseña es

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

o alguna formación equivalente. Sin embargo, este tipo de cosas tiene éxito silenciosamente cuando se pasa un puntero y da resultados que pueden parecer plausibles en el tiempo de ejecución hasta que las cosas se desintegran misteriosamente.

Es muy fácil cometer este error: una función que tiene una variable de matriz local se refacta, moviendo un poco de manipulación de matriz a una nueva función llamada con la matriz como parámetro.

Entonces, la pregunta es: ¿hay una macro "sanitaria" para detectar el mal uso de la macro ARRAYSIZE en C, preferiblemente en tiempo de compilación? En C ++ solo usaríamos una plantilla especializada solo para argumentos de matriz; en C, parece que necesitaremos alguna forma de distinguir matrices y punteros. (Si quisiera rechazar matrices, por ejemplo, solo haría, por ejemplo, (arr=arr, ...) porque la asignación de matrices es ilegal).


Aquí hay una solución posible utilizando una extensión GNU llamada expresiones de declaración :

#define ARRAYSIZE(arr) / ({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; / sizeof(arr) / sizeof((arr)[0]);})

Esto utiliza una aserción estática para afirmar que sizeof(arr) != sizeof(void*) . Esto tiene una limitación obvia: no puede usar esta macro en matrices cuyo tamaño sea exactamente de un puntero (por ejemplo, una matriz de 1 longitud de punteros / enteros, o tal vez una matriz de 4 longitudes de bytes en una red de 32 bits). plataforma). Pero esos casos particulares pueden ser resueltos fácilmente.

Esta solución no es portátil para plataformas que no admiten esta extensión GNU. En esos casos, recomiendo usar la macro estándar y no preocuparse por pasar accidentalmente los punteros a la macro.


Con C11, podemos diferenciar matrices y punteros usando _Generic , pero solo he encontrado una forma de hacerlo si suministras el tipo de elemento:

#define ARRAY_SIZE(A, T) / _Generic(&(A), / T **: (void)0, / default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0]))) int a[2]; printf("%zu/n", ARRAY_SIZE(a, int));

La macro comprueba: 1) el puntero a A no es puntero a puntero. 2) puntero a elemento es puntero a T. Se evalúa a (void)0 y falla estáticamente con los punteros.

Es una respuesta imperfecta, pero tal vez un lector pueda mejorarla y deshacerse de ese tipo de parámetro.


El kernel de Linux usa una buena implementación de ARRAY_SIZE para tratar este problema:

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

con

#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))

y

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

Por supuesto, esto solo es portátil en GNU C, ya que utiliza dos instrumentos: el operador typeof y la función __builtin_types_compatible_p . También utiliza su "famosa" macro BUILD_BUG_ON_ZERO que solo es válida en GNU C.

Suponiendo un requisito de tiempo de compilación (que es lo que queremos), no conozco ninguna implementación portátil de esta macro.

Una implementación "semi-portátil" (y que no cubriría todos los casos) es:

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

con

#define IS_ARRAY(arr) ((void*)&(arr) == &(arr)[0]) #define STATIC_EXP(e) / (0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))

Con gcc esto no da ninguna advertencia si el argumento es una matriz en -std=c99 -Wall pero -pedantic daría una advertencia. El motivo es que la expresión IS_ARRAY no es una expresión constante de entero (los tipos de IS_ARRAY a puntero y el operador de subíndice no están permitidos en las expresiones de constantes de enteros) y el ancho del campo de bits en STATIC_EXP requiere una expresión constante de enteros.


Es horrible, sí, pero eso funciona y es portátil.

#define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? / (sizeof(arr)/sizeof(*arr)) : / -1+0*fprintf(stderr, "/n/n** pointer in ARRAYSIZE at line %d !! **/n/n", __LINE__))

Esto no detectará nada en el momento de la compilación pero imprimirá un mensaje de error en stderr y devolverá -1 si es un puntero o si la longitud de la matriz es 1.

==> DEMO <==


Esta versión de ARRAYSIZE() devuelve 0 cuando arr es un puntero y el tamaño cuando es una matriz pura

#include <stdio.h> #define IS_INDEXABLE(arg) (sizeof(arg[0])) #define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg))) #define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0) int main(void) { int a[5]; int *b = a; int n = 10; int c[n]; /* a VLA */ printf("%zu/n", ARRAYSIZE(a)); printf("%zu/n", ARRAYSIZE(b)); printf("%zu/n", ARRAYSIZE(c)); return 0; }

Salida:

5 0 10

Como lo señaló Ben Jackson, puede forzar una excepción de tiempo de ejecución (dividiendo por 0)

#define IS_INDEXABLE(arg) (sizeof(arg[0])) #define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg))) #define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0))

Lamentablemente, no puede forzar un error en tiempo de compilación (la dirección de arg debe compararse en tiempo de ejecución)


Mi favorito personal, probé gcc 4.6.3 y 4.9.2:

#define STR_(tokens) # tokens #define ARRAY_SIZE(array) / ({ / _Static_assert / ( / ! __builtin_types_compatible_p(typeof(array), typeof(& array[0])), / "ARRAY_SIZE: " STR_(array) " [expanded from: " # array "] is not an array" / ); / sizeof(array) / sizeof((array)[0]); / }) /* * example */ #define not_an_array ((char const *) "not an array") int main () { return ARRAY_SIZE(not_an_array); }

impresiones del compilador

x.c:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) /"not an array/") [expanded from: not_an_array] is not an array"


Modificación de la respuesta de Bluss usando typeof en lugar de un parámetro de tipo:

#define ARRAY_SIZE(A) / _Generic(&(A), / typeof((A)[0]) **: (void)0, / default: sizeof(A) / sizeof((A)[0]))


Un ejemplo más para la colección.

#define LENGTHOF(X) ({ / const size_t length = (sizeof X / (sizeof X[0] ?: 1)); / typeof(X[0]) (*should_be_an_array)[length] = &X; / length; })

Pros:

  1. Funciona con matrices normales, matrices de longitud variable, matrices multidimensionales, matrices de estructuras de tamaño cero
  2. Genera un error de compilación (no advertencia) si pasa algún puntero, estructura o unión
  3. No depende de ninguna de las características del C11.
  4. Te da error muy legible

Contras:

  1. Depende de algunas de las extensiones de gcc: Typeof , Statement Exprs y (si te gusta) Conditionals
  2. Depende de la característica VLA C99.