punteros puntero matrices declaracion cadenas aritmetica c++ c

c++ - matrices - ¿Por qué usar el tamaño de matriz 1 en lugar de puntero?



punteros en c (6)

En un proyecto de código abierto de C ++, veo esto.

struct SomeClass { ... size_t data_length; char data[1]; ... }

¿Cuáles son las ventajas de hacerlo en lugar de usar un puntero?

struct SomeClass { ... size_t data_length; char* data; ... }

Lo único en lo que puedo pensar es con la versión de matriz de tamaño 1, no se espera que los usuarios vean NULL. ¿Hay algo mas?


  1. La estructura puede asignarse simplemente como un único bloque de memoria en lugar de múltiples asignaciones que deben liberarse.

  2. En realidad usa menos memoria porque no necesita almacenar el puntero en sí.

  3. También puede haber ventajas de rendimiento con el almacenamiento en caché debido a que la memoria es contigua.


Con esto, no tiene que asignar la memoria a otro lugar y hacer que el puntero apunte a eso.

  • Sin gestión de memoria extra
  • Los accesos a la memoria llegarán a la memoria caché (es mucho más probable)

El truco es asignar más memoria que sizeof (SomeClass) , y hacer que SomeClass* apunte a ella. Luego, la memoria inicial será utilizada por su objeto SomeClass , y la memoria restante puede ser utilizada por los data . Es decir, puede decir p->data[0] pero también p->data[1] y así sucesivamente hasta que llegue al final de la memoria que asignó.

Sin embargo, se puede hacer que este uso resulte en un comportamiento indefinido, ya que declaró que su matriz solo tiene un elemento, pero que accede como si contuviera más. Pero los compiladores reales permiten esto con el significado esperado porque C ++ no tiene una sintaxis alternativa para formular estos medios (C99 tiene, se llama "miembro de matriz flexible" allí).


Esta suele ser una forma rápida (y sucia?) De evitar múltiples asignaciones de memoria y desasignaciones, aunque es más elegante que C ++.

Es decir, en lugar de esto:

struct SomeClass *foo = malloc(sizeof *foo); foo->data = malloc(data_len); memcpy(foo->data,data,data_len); .... free(foo->data); free(foo);

Haces algo como esto:

struct SomeClass *foo = malloc(sizeof *foo + data_len); memcpy(foo->data,data,data_len); ... free(foo);

Además de guardar (des) las llamadas de asignación, esto también puede ahorrar un poco de memoria ya que no hay espacio para un puntero e incluso podría usar espacio que de otro modo podría haber sido relleno de estructura.


La idea detrás de esta cosa en particular es que el resto de los data caben en la memoria directamente después de la estructura. Por supuesto, podrías hacer eso de todos modos.


Normalmente ves esto como el miembro final de una estructura. Luego, cualquiera que sea la estructura de malloc , asignará todos los bytes de datos consecutivamente en la memoria como un bloque para "seguir" la estructura.

Entonces, si necesita 16 bytes de datos, asignaría una instancia como esta:

SomeClass * pObj = malloc(sizeof(SomeClass) + (16 - 1));

Luego puedes acceder a los datos como si fuera una matriz:

pObj->data[12] = 0xAB;

Y puedes liberar todas las cosas con una llamada, por supuesto, también.

El miembro de data es una matriz de un solo elemento por convención porque los compiladores de C más antiguos (y aparentemente el estándar de C ++ actual) no permiten una matriz de tamaño cero. Buena discusión adicional aquí: http://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html


Son semánticamente diferentes en tu ejemplo.

char data[1] son una matriz válida de char con un elemento sin inicializar asignado en la pila. Podría escribir data[0] = ''w'' y su programa sería correcto.

char* data; simplemente declara un puntero que no es válido hasta que se inicializa para apuntar a una dirección válida.