2008-11-17 17 views
48

Estoy trabajando en la refacturación de algunos códigos antiguos y he encontrado algunas estructuras que contienen matrices de longitud cero (a continuación). 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 que disfrute el proceso ... ¿Alguna de las causas para usar tal cosa? ¿Algún consejo para refaccionar esos?Matriz de longitud cero

struct someData 
{ 
    int nData; 
    BYTE byData[0]; 
} 

NB es C++, Windows XP, VS 2003

+3

Este es el "struct hack", descrito en la pregunta 2.6 de [comp.lang.c FAQ] (http://www.c-faq.com/). Dennis Ritchie lo llamó "amistad sin justificación con la implementación C". C99 introdujo una nueva función de lenguaje, el "miembro de matriz flexible", para reemplazar el hack de estructura. Incluso el compilador de Microsoft, que se destaca por su falta de compatibilidad con C99, admite miembros de matriz flexibles. –

+0

NO agregue la etiqueta 'c' a esta pregunta.Las reglas de C++ para esto son bastante diferentes de las reglas de C. –

+0

@BenVoigt La respuesta aceptada es código C puro, así que supongo que su edición es incorrecta. c hack se aplica tanto a c como a C++ de la misma manera –

Respuesta

33

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 usted tienen un objeto de somedata con una matriz de una longitud especificada.

+0

¿No debería al menos usar 'new []', siendo esto sobre C++? – unwind

+1

@unwind: No se puede usar nuevo para esto. El punto es que este es un C-Hack y no se requiere en C++ (porque tenemos mejores formas de hacerlo). También estoy bastante seguro de que las matrices de longitud cero son ilegales en C++ (al menos C++ 03, no estoy seguro si eso se actualizó en C++ 11). –

+0

La jerga popular para esto es "Struct Hack". –

22

Ésta es una vieja C truco para permitir que unos matrices de tamaño flexibles.

En el estándar C99 esto no es necesario ya que admite la sintaxis arr [].

+3

Lamentablemente, Visual Studio es muy pobre cuando se trata de compatibilidad con C99. :( –

+5

Sin abordar la verdad general de su comentario, ... el compilador MS VC v9 admite la sintaxis arr [] – Cheeso

23

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.

Nota 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 en los estándares. Su publicación debe aplicarse a su problema.

http://blogs.msdn.com/oldnewthing/archive/2004/08/26/220873.aspx

EDITAR

Nota: A pesar de que el puesto de Raymond dice 0 matrices de longitud son legales en C99 son, de hecho, todavía no es legal en C99. En lugar de una matriz de 0 longitudes aquí, debería usar una matriz de longitud 1

+1

"* Este es el caso porque las matrices de longitud cero son una entrada más reciente a los estándares. *" estándares? C++ 11 aún no permite matrices de 0-length (§8.3.4/1), así como también C99 (§6.7.5.2/1). – ildjarn

+0

@ildjarn Estaba esencialmente analizando lo que Raymond dijo al final de su blog No sabía que las matrices de 0 longitudes seguían siendo ilegales en C99 hasta una discusión reciente con usted sobre otra pregunta. Actualizaré la respuesta – JaredPar

+0

Lamento contestar que una respuesta tan antigua.: -PI solo pregunta porque otra pregunta se vinculó aquí como "prueba" de que las matrices de 0-longitud eran legales C++.: -] – ildjarn

8

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 truco, debemos escabullirlo más allá del compilador.

La forma correcta de hacer el "truco struct C" (que es compatible con dialectos C volviendo a 1989 ANSI C, y probablemente mucho antes) es el uso de 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 de byData se calcula usando:

offsetof(struct someData, byData); 

para asignar un struct someData con espacio para 42 bytes en byData, entonces debería utilizar:

struct someData *psd = (struct someData *) malloc(offsetof(struct someData, byData) + 42); 

Tenga en cuenta que este cálculo offsetof es de hecho el cálculo correcto, incluso en el caso del tamaño de la matriz es cero. Usted ve, 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 */ 
}; 

El tamaño de struct hack es posiblemente acolchada para la alineación debido a la miembro de ul. Si unsigned long tiene cuatro bytes de ancho, muy 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 utilizar 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 struct en una interfaz de pase 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.

+0

"que es compatible con dialectos C que datan de 1989" - acceder al pasado el primer elemento de la matriz provoca un comportamiento indefinido incluso en C89. El struct hack se basa en que el compilador "defina" este comportamiento por sí mismo. –

0

Vale la pena indicar 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.

Cuestiones relacionadas