Matriz de longitud cero
Estoy trabajando para refactorizar un código antiguo y encontré algunas estructuras que contienen matrices de longitud cero (a continuación). Advertencias deprimidas por pragma, por supuesto, pero no pude crear estructuras "nuevas" que contengan tales estructuras (error 2233). La matriz 'byData' se usa como puntero, pero ¿por qué no usar un puntero en su lugar? o matriz de longitud 1? Y por supuesto, no se agregaron comentarios para hacerme disfrutar el proceso... ¿Alguna causa para usar tal cosa? ¿Algún consejo para refactorizarlos?
struct someData
{
int nData;
BYTE byData[0];
}
Nota: Es C++, Windows XP, VS 2003.
Sí, este es un C-Hack.
Para crear una matriz de cualquier longitud:
struct someData* mallocSomeData(int size)
{
struct someData* result = (struct someData*)malloc(sizeof(struct someData) + size * sizeof(BYTE));
if (result)
{ result->nData = size;
}
return result;
}
Ahora tienes un objeto de someData con una matriz de una longitud especificada.
Desafortunadamente, existen varias razones por las que se declararía una matriz de longitud cero al final de una estructura. Básicamente, le brinda la posibilidad de obtener una estructura de longitud variable devuelta desde una API.
Raymond Chen hizo una excelente publicación en su blog sobre el tema. Te sugiero que eches un vistazo a esta publicación porque probablemente contenga la respuesta que deseas.
Tenga en cuenta que en su publicación se trata de matrices de tamaño 1 en lugar de 0. Este es el caso porque las matrices de longitud cero son una entrada más reciente a los estándares. Su publicación aún debería aplicarse a su problema.
http://blogs.msdn.com/oldnewthing/archive/2004/08/26/220873.aspx
EDITAR
Nota: Aunque la publicación de Raymond dice que las matrices de longitud 0 son legales en C99, de hecho todavía no lo son en C99. En lugar de una matriz de longitud 0 aquí deberías usar una matriz de longitud 1
Este es un antiguo truco de C para permitir matrices de tamaño flexible.
En el estándar C99 esto no es necesario ya que admite la sintaxis arr[].
Su intuición sobre "por qué no utilizar una matriz de tamaño 1" es acertada.
El código está haciendo mal el "truco de estructura C", porque las declaraciones de matrices de longitud cero son una violación de restricción. Esto significa que un compilador puede rechazar su pirateo de inmediato en el momento de la compilación con un mensaje de diagnóstico que detiene la traducción.
Si queremos perpetrar un hack, debemos pasarlo a escondidas por el compilador.
La forma correcta de hacer el "truco de estructura C" (que es compatible con dialectos C que se remontan a ANSI C de 1989, y probablemente mucho antes) es utilizar una matriz perfectamente válida de tamaño 1:
struct someData
{
int nData;
unsigned char byData[1];
}
Además, en lugar de sizeof struct someData
, el tamaño de la pieza anterior byData
se calcula utilizando:
offsetof(struct someData, byData);
Para asignar un struct someData
espacio para 42 bytes byData
, usaríamos:
struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42);
Tenga en cuenta que este offsetof
cálculo es, de hecho, el cálculo correcto incluso en el caso de que el tamaño de la matriz sea cero. Verás, sizeof
toda la estructura puede incluir relleno. Por ejemplo, si tenemos algo como esto:
struct hack {
unsigned long ul;
char c;
char foo[0]; /* assuming our compiler accepts this nonsense */
};
Es muy posible que el tamaño struct hack
esté acolchado para alinearlo debido al ul
miembro. Si unsigned long
tiene cuatro bytes de ancho, entonces es muy posible sizeof (struct hack)
que sea 8, mientras que offsetof(struct hack, foo)
es casi seguro que sea 5. El offsetof
método es la forma de obtener el tamaño exacto de la parte anterior de la estructura justo antes de la matriz.
Entonces esa sería la manera de refactorizar el código: hacerlo conforme al clásico y altamente portátil truco de estructura.
¿Por qué no utilizar un puntero? Porque un puntero ocupa espacio adicional y debe inicializarse.
Hay otras buenas razones para no utilizar un puntero, a saber, que un puntero requiere un espacio de direcciones para ser significativo. El truco de estructura es externalizable: es decir, hay situaciones en las que dicho diseño se ajusta al almacenamiento externo, como áreas de archivos, paquetes o memoria compartida, en las que no desea punteros porque no son significativos.
Hace varios años, utilicé el truco de estructura en una interfaz de paso de mensajes de memoria compartida entre el kernel y el espacio del usuario. No quería punteros allí, porque habrían sido significativos sólo para el espacio de direcciones original del proceso que genera un mensaje. La parte del núcleo del software tenía vista a la memoria usando su propio mapeo en una dirección diferente, por lo que todo se basó en cálculos de compensación.