Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.5.2  Iniciación de estructuras

§1  Sinopsis

Se ha señalado que, al igual que las matrices, las estructuras pueden iniciarse, incluso en el mismo punto de su declaración, con una lista de iniciadores entre corchetes { } separados por comas [1], uno para cada miembro de la estructura. Por ejemplo, la sentencia:

struct Cliente {int i; char str[20]; double d;} s = {33, "Pepe Lopez", 3.14 };

declara una estructura Cliente compuesta por un entero; un array de 20 caracteres, y un doble.También inicia una estructura s como perteneciente al tipo Cliente con valores concretos en cada campo.


§2  En estructuras o uniones con duración automática ( 4.1.8a), el iniciador debe ser alguno de los siguientes:

  • Una lista de inicializadores constantes (también se permiten expresiones con sizeof) Ejemplo:

    struct punto {int x; int y;} p1 = {1, 2};

  • Una sola expresión con una estructura de tipo compatible.  En este caso, el valor inicial del objeto es el de la expresión.  Ejemplo:

    struct punto p2 = p1;

    en este caso, (suponiendo los valores anteriores) sería: p2.x == 1 y p2.y == 2


§3  Los miembros complejos de listas de iniciadores. Por ejemplo matrices, pueden inicializarse con expresiones adecuadas incluidas en bloques de corchetes anidados. Por ejemplo:

struct Pesadas {

   int tiket;

   int pesos[5];

} s = { 150, {10, 45, 0, 78, 20}};

Si la lista de inicializadores contenidos entre los corchetes { } es menor que los miembros de la estructura, el resto de los miembros es inicializado implícitamente siguiendo las mismas reglas que los objetos con duración estática.

§4  Comentarios

Las estructuras, al igual que las matrices, almacenan sus miembros de forma contigua, razón por la cual, como veremos más adelante, se les pueden aplicar punteros y una cierta aritmética. Por la misma razón, se pueden crear matrices de estructuras e incluso estructuras de matrices (sus miembros son matrices). Conceptualmente, estas últimas no se diferencian gran cosa de las matrices de matrices (a no ser en la notación de acceso a sus miembros). También se puede calcular su tamaño en bytes con la expresión sizeof, aunque a este respecto debe tenerse en cuenta algunas posibles discrepancias respecto a los valores "teóricos", debidas a las alineaciones internas realizadas por el compilador, que se comentan .


§4.1  Se ha dicho que los miembros de estructuras pueden ser de cualquier tipo, incluso otras estructuras ( 4.5.6 Matrices de estructuras). Sin embargo, existen varias limitaciones, sobre todo las que se opongan al principio anterior. Por ejemplo, la declaración que sigue conduce a un error:

struct str {int x; char * psch = "Hola";};  // Error!


La razón ya expuesta (4.5.1), que en la definición de un tipo no pueden realizarse asignaciones, tiene su base en el comentario anterior. En efecto: en "Constantes de cadena" ( 3.2.3f), se indicó que ante la expresión char* psch = "Hola";, el compilador debe crear una matriz de caracteres "Hola\0"; almacenarlo en algún sitio, y asignar a una variable tipo puntero a carácter psch (dirección del primer elemento de la matriz). Esto conduciría a que no se podría garantizar que los miembros estuvieran íntegramente en la estructura.

La expresión anterior sería admisible con una modificación del tipo:

char* psch = "Hola";
struct str {int x; char* psch;};


  pero ¡Atención!, con este esquema, el miembro psch de str no tiene nada que ver con el puntero a carácter psch de la primera línea ( 4.5.1d  Espacio de nombres de estructuras ). str.psch sigue siendo un puntero a carácter genérico.


§4.2
  Una alternativa es efectuar la asignación en una variable concreta (en POO diríamos en una instancia de la clase str):

char * psch = "Hola";

struct str {int x; char * psch;};   // define el tipo de estructura str

struct str strA;                    // declara strA estructura tipo str

strA.psch = psch;                nbsp;  // asigna valor

Podemos efectuar una comprobación de la asignación:

printf("%s\n", strA.psch);   // -> Hola


Hay que hacer hincapié en que el miembro strA.psch (puntero a carácter) apunta ahora a la dirección de una zona de memoria donde está almacenada la cadena "Hola\0", dirección que coincide con la indicada por el puntero [2] psch. Si cambiamos el valor de psch, el del miembro no se verá afectado, lo que podemos comprobar añadiendo estas sentencias:

char * psch = "Lola";
printf("%s\n", strA.psch);    // -> Hola

El resultado evidencia que la cadena "Hola" sigue existiendo en alguna posición de memoria y es accedida mediante strA.psch.

Se habría obtenido un resultado distinto si en vez de las dos sentencias anteriores modificamos directamente la posición de memoria donde está alojada la cadena "Hola". Lo hacemos mediante:

*psch = 'L';  *(psch+3) = 'o';
printf("%s\n", strA.psch);    // -> Lolo


Observe que el paréntesis *(psch+3) es necesario, ya que *psch+3 es interpretado como (*psch)+3 = 'K'. Con lo que el intento de asignación 'K' = 'o' produce un error del compilador: No es un Lvalue. Ya vimos que el miembro a la izquierda de una asignación debe ser un Lvalue ( 2.1.5).


§4.3
  Una variante de la declaración anterior, señalada (§5.1 ) es la siguiente:

struct str {int x; char * psch;}; // define el tipo de estructura str

struct str strA = {1, "Hola"};    // declara e inicia strA

printf("%s\n", strA.psch);        // salida:  Hola

La situación final es análoga a la allí expuesta; el resultado es una cadena "Hola\0" almacenada en algún sitio y un puntero a carácter strA.psch apuntando a ella, como miembro de la estructura strA. Podemos comprobarlo modificando el contenido de las posiciones de memoria que alojan la cadena mediante:

*strA.psch = 'L';  *(strA.psch+3) = 'o';
printf("%s\n", strA.psch);    // -> Lolo

§5  Espacio de almacenamiento

A continuación se expone un ejemplo de estructura con una composición de miembros variada. En el comentario de cada línea se indica el espacio de almacenamiento necesario en bytes [3] según las especificaciones declaradas para el compilador utilizado ( 2.2.4 Representación interna y rango):

struct general {

  int x;              // L2.  4 bytes

  char ch;            // L3.  1 byte

  double db;          // L4.  8 bytes

  char * sg;          // L5.  4 bytes

  char nom[30];       // L6. 30 bytes

  char * dir[];       // L7.  4 bytes

} str;                // L8.

printf("M1:%3.0d\n", sizeof(str.x));      // L9.  -> M1:  4

printf("M2:%3.0d\n", sizeof(str.ch));     // L10. -> M2:  1

printf("M3:%3.0d\n", sizeof(str.db));     // L11. -> M3:  8

printf("M4:%3.0d\n", sizeof(str.sg));     // L12. -> M4:  4

printf("M5:%3.0d\n", sizeof(str.nom));    // L13. -> M5: 30

printf("M6:%3.0d\n", sizeof(str.dir[0])); // L14. -> M6:  4

printf("Total:%3.0d\n", sizeof(str));     // L15. -> Total: 56


Las líneas 2, 3 y 4 no requieren comentario; L5 define un puntero a carácter, ocupará 4 bytes en un compilador de 32 bits ( 4.2  Punteros). L6 define una matriz de 30 caracteres, por lo que se reserva espacio para otros tantos. Finalmente, L7 define un puntero a matriz de caracteres; este miembro ocupa un espacio de 4 bytes en la estructura, aunque la matriz a la que apunta (exterior a la propia estructura) sea de tamaño indefinido.

Las líneas 9 a 14 son simplemente una confirmación de que las suposiciones teóricas son correctas, como efectivamente se comprueba. La línea 15 es una comprobación del tamaño total de la estructura, con el sorprendente resultado que se indica (la compilación se ha realizado con la opción de alineación por defecto -a4 4.5.9a).

La razón de la discrepancia hay que buscarla en el hecho de que, por razones de eficiencia (por ejemplo de velocidad de acceso a memoria), el compilador, que asigna espacio de memoria en múltiplos de de un cierto número determinado de bits (8 en este caso), intenta no utilizar fracciones de palabra, con lo que realiza determinados redondeos o alineaciones internas ( 4.5.9a). En este caso, la suma teórica: 51 x 8 = 408 bits habría supuesto el uso de 12.75 palabras de 32 bits, por lo que el compilador redondea a 14 palabras; 56 x 8 = 448 bits, 14 palabras. Hay que recordar que, aunque existan 5 bytes perdidos (no utilizados), el almacenamiento ocupado por la estructura str sigue siendo contiguo, y el acceso a sus miembros totalmente transparente para el programador. Del mismo modo, si más tarde se declara una matriz de estructuras de tipo general y se utilizan punteros, el compilador tiene automáticamente en cuenta el verdadero tamaño ocupado por cada elemento, a fin de efectuar correctamente los desplazamientos pertinentes (Aritmética de punteros 4.2.2).

Nota:  tanto el compilador Borlanc C++ 5.5 como el MS Visual C++ permiten establecer alineaciones en múltiplos de 1, 2, 4, 8 y 16 octetos.


Como puede verse, en términos de tamaño de la estructura, las estrategias representadas por la líneas 6 y 7 varían grandemente. La adopción de una u otra, depende del contexto de cada caso. Téngase en cuenta, por ejemplo, que si se guardase en disco el contenido de la estructura, una recuperación posterior garantizaría el contenido original para el elemento str.nom. Sin embargo, los punteros str.sg y str.dir quizás podrían apuntar a zonas de memoria erróneas.

  Inicio.


[1]  No confundir con la definición de la propia estructura, donde los miembros aparecen separados por punto y coma.

[2]  Nótese que esto no contradice el principio de que los miembros deben estar contenidos en la estructura y ocupar zonas contiguas de almacenamiento: El puntero "pertenece a" la estructura y su valor es contiguo con el resto de los miembros, otra cosa es que este valor pueda ser considerado la dirección de una zona de memoria arbitraria (incluso inexistente) en la máquina.

[3]  Suponemos que se utiliza el Compilador C++ 5.5 para Windows 32 de Borland.