c++ - Matriz de longitud cero
arrays visual-c++ (5)
Desafortunadamente, hay varias razones por las que declararía una matriz de longitud cero al final de una estructura. En esencia, le brinda la capacidad de tener una estructura de longitud variable devuelta por una API.
Raymond Chen hizo una excelente publicación en el 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 debe 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 0 longitudes son legales en C99, de hecho aún no son legales en C99. En lugar de una matriz de 0 longitudes aquí, debería usar una matriz de longitud 1
Estoy trabajando en la refacturación de algunos códigos antiguos y he encontrado algunas estructuras que contienen matrices de longitud cero (abajo). Advertencias deprimidas por pragma, por supuesto, pero no he podido crearlas con estructuras "nuevas" que contengan tales estructuras (error 2233). Matriz ''byData'' utilizada como puntero, pero ¿por qué no usar puntero? o una matriz de longitud 1? Y, por supuesto, no se agregaron comentarios para hacerme disfrutar del proceso ... ¿Alguna de las causas para usar tal cosa? ¿Algún consejo para refaccionar esos?
struct someData
{
int nData;
BYTE byData[0];
}
NB Es C ++, Windows XP, VS 2003
Este es un viejo 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 [].
Sí, esto 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 tiene un objeto de someData con una matriz de una longitud especificada.
Su intuición sobre "por qué no usar una matriz de tamaño 1" es perfecta.
El código está haciendo el "C struct hack" incorrecto, porque las declaraciones de arrays de longitud cero son una violación de restricción. Esto significa que un compilador puede rechazar su truco inmediatamente en el momento de la compilación con un mensaje de diagnóstico que detiene la traducción.
Si queremos perpetrar un ataque, debemos pasarlo a escondidas del compilador.
La forma correcta de hacer el "C struct hack" (que es compatible con los dialectos C desde 1989 ANSI C, y probablemente mucho antes) es usar 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 parte antes byData
se calcula usando:
offsetof(struct someData, byData);
Para asignar una struct someData
con espacio para 42 bytes en byData
, byData
:
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. Usted ve, el tamaño de 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 */
};
El tamaño de struct hack
es muy posiblemente rellenado para la alineación debido al miembro ul
. Si unsigned long
tiene cuatro bytes de ancho, entonces posiblemente sizeof (struct hack)
es 8, mientras que offsetof(struct hack, foo)
es casi con seguridad 5. El método offsetof
es la forma de obtener el tamaño exacto de la parte anterior de la estructura justo antes de la matriz.
Así que esa sería la forma de refactorizar el código: hacerlo conforme al clásico y altamente portable struct hack.
¿Por qué no usar un puntero? Porque un puntero ocupa espacio extra y debe inicializarse.
Existen otras buenas razones para no utilizar un puntero, a saber, que un puntero requiere un espacio de direcciones para que sea significativo. El struct hack 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 el que no se desean punteros porque no son significativos.
Hace varios años, utilicé el hack de estructuras en una interfaz de paso de mensajes de memoria compartida entre kernel y espacio de usuario. No quería punteros allí, porque solo habrían sido significativos para el espacio de direcciones original del proceso que generaba un mensaje. La parte del kernel del software tenía una vista hacia la memoria usando su propia asignación en una dirección diferente, y así todo estaba basado en cálculos de compensación.
Vale la pena señalar a IMO la mejor manera de hacer el cálculo de tamaño, que se utiliza en el artículo de Raymond Chen vinculado anteriormente.
struct foo
{
size_t count;
int data[1];
}
size_t foo_size_from_count(size_t count)
{
return offsetof(foo, data[count]);
}
El desplazamiento de la primera entrada al final de la asignación deseada también es el tamaño de la asignación deseada. IMO es una forma extremadamente elegante de hacer el cálculo de tamaño. No importa cuál sea el tipo de elemento de la matriz de tamaño variable. El offsetof (o FIELD_OFFSET o UFIELD_OFFSET en Windows) siempre se escribe de la misma manera. No hay expresiones sizeof () para desordenar accidentalmente.