Macro de tamaño de matriz que rechaza punteros
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 tiempo de ejecución hasta que las cosas se desmoronan misteriosamente.
Es muy fácil cometer este error: una función que tiene una variable de matriz local se refactoriza, moviendo un poco de manipulación de la matriz a una nueva función llamada con la matriz como parámetro.
Entonces, la pregunta es: ¿existe una macro "sanitaria" para detectar el uso indebido de la ARRAYSIZE
macro en C, preferiblemente en tiempo de compilación? En C++ simplemente 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, lo haría, por ejemplo, (arr=arr, ...)
porque la asignación de matrices es ilegal).
El kernel de Linux utiliza una buena implementación para ARRAY_SIZE
solucionar 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 es portátil sólo en GNU C ya que utiliza dos elementos intrínsecos:
typeof
operador y __builtin_types_compatible_p
función. También utiliza su "famosa" BUILD_BUG_ON_ZERO
macro que sólo es válida en GNU C.
Suponiendo un requisito de evaluación en tiempo de compilación (que es lo que queremos), no conozco ninguna implementación portátil de esta macro.
Una implementación "semiportá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 se da ninguna advertencia si el argumento es una matriz, -std=c99 -Wall
pero -pedantic
sí se da una advertencia. La razón es que IS_ARRAY
la expresión no es una expresión constante entera (la conversión a tipos de puntero y el operador de subíndice no están permitidos en expresiones constantes enteras) y el ancho del campo de bits STATIC_EXP
requiere una expresión constante entera.
Esta versión devuelve ARRAYSIZE()
cuándo 0
es arr
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;
}
Producción:
5
0
10
Como 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 se puede forzar un error en tiempo de compilación (la dirección debe arg
compararse en tiempo de ejecución)
Con C11, podemos diferenciar matrices y punteros usando _Generic
, pero solo encontré una manera de hacerlo si proporcionas 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 un puntero a puntero. 2) puntero a elemento es puntero a T. Se evalúa (void)0
y falla estáticamente con punteros.
Es una respuesta imperfecta, pero tal vez un lector pueda mejorarla y deshacerse de ese parámetro de tipo.
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]))