Iterando sobre miembros de estructura del mismo tipo en C

6 minutos de lectura

Iterando sobre miembros de estructura del mismo tipo en C
Misha M

¿Es posible iterar una estructura C, donde todos los miembros son del mismo tipo, usando un puntero? Aquí hay un código de muestra que no se compila:

#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    int mem1 ;
    int mem2 ;
    int mem3 ;
    int mem4 ;
} foo ;

void my_func( foo* data )
{
    int i ;
    int* tmp = data ; // This line is the problem

    for( i = 0; i < 4; ++i )
    {
        ++tmp ;
        printf( "%d\n", *tmp ) ;
    }
}

int main()
{
    foo my_foo ;
    //
    my_foo.mem1 = 0 ;
    my_foo.mem2 = 1 ;
    my_foo.mem3 = 2 ;
    my_foo.mem4 = 3 ;
    //
    my_func( &my_foo ) ;
    return 0 ;
}

Los miembros de foo deben estar alineados en la memoria uno tras otro, suponiendo que su compilador/núcleo no intente proporcionar protección de pila para el desbordamiento del búfer.

Entonces mi pregunta es:

¿Cómo iteraría sobre los miembros de una estructura C que son del mismo tipo?

  • ¿Has probado con un yeso, como int * tmp = (int *)data'? ¿Qué mensaje de error te sale?

    –David Thornley

    8 de diciembre de 2009 a las 21:42

  • David, con la última actualización, el código funciona ahora. Esa asignación produce una advertencia de conversión del compilador. Esperaba que hubiera una solución más “elegante”.

    – Misha M.

    8 de diciembre de 2009 a las 22:03

  • De acuerdo con la especificación del lenguaje C, no existe ningún requisito de que los campos de una estructura estén uno al lado del otro en la memoria; los compiladores pueden insertar relleno entre los miembros del campo. Si desea iteración, use una matriz.

    – Thomas Matthews

    9 de diciembre de 2009 a las 21:01

La mayoría de los intentos de usar una unión con una matriz son propensos a fallar. Tienen una buena posibilidad de funcionar siempre que solo use int, pero para otros tipos, especialmente más pequeños, es probable que fallen con bastante frecuencia porque el compilador puede (y especialmente con tipos más pequeños a menudo lo hará) agregar relleno entre los miembros de a structpero no está permitido hacerlo con elementos de una matriz).

C, sin embargo, tiene un offsetof() macro que puede utilizar. Produce el desplazamiento de un elemento en un struct, por lo que puede crear una matriz de compensaciones, luego (con un poco de cuidado en la conversión) puede agregar esa compensación a la dirección de la estructura para obtener la dirección del miembro. El cuidado en la conversión se debe a que el desplazamiento está en bytes, por lo que debe convertir la dirección del struct para char *luego agregue el desplazamiento, luego convierta el resultado al tipo del miembro (int en tu caso).

  • Jerry, gracias. Investigaré offsetof(). Esto suena como la mejor solución que usar una unión.

    – Misha M.

    8 de diciembre de 2009 a las 22:05

Desde el punto de vista del lenguaje: no se puede. Los miembros de datos de la estructura no son… er… “iterables” en C, independientemente de si son del mismo tipo o de tipos diferentes.

Use una matriz en lugar de un grupo de miembros independientes.

  • Una lista o un mapa en realidad funcionaría mejor aquí. A veces, no es posible cambiar la arquitectura del código, por lo que hay que conformarse con lo que hay. No estoy de acuerdo contigo en cuanto al punto de vista del lenguaje. A C, como se define el lenguaje, no le importa.

    – Misha M.

    8 de diciembre de 2009 a las 21:15

  • @Misha: al lenguaje, tal como se define, le importa si tiene una matriz o una estructura. Puede iterar a través de una matriz, pero no de una estructura.

    –David Thornley

    8 de diciembre de 2009 a las 21:39

La forma más fácil sería crear una unión, una parte que contenga cada miembro individualmente y una parte que contenga una matriz. No estoy seguro de si el relleno dependiente de la plataforma podría interferir con la alineación.

  • Gracias por esa idea, me olvidé por completo de usar una unión aquí. Me preocupan las dependencias de la arquitectura aquí, aunque todas son de 32 bits, por lo que debería estar bien. Mi preocupación al modificar la definición de la estructura sería el impacto en el resto del código que la usa. Tener que recompilar bibliotecas y otras dependencias.

    – Misha M.

    8 de diciembre de 2009 a las 21:17

  • Como se indica a continuación, si bien esto funcionará con muchos compiladores y plataformas, puede fallar en otros. Haga un favor a sus mantenedores y escriba pruebas unitarias para esta iteración, de modo que cuando se rompa en algún sistema extraño algún día, sea inmediatamente obvio.

    – emk

    8 de diciembre de 2009 a las 21:40

  • Estoy de acuerdo, al menos algo como assert((void*)&my_foo.s.mem4 == (void*)&my_foo.a.mem[3]);

    – Mark Ransom

    8 de diciembre de 2009 a las 21:47

También puede usar una unión/estructura sin nombre:

struct foo {
    union {
        struct {
            int mem1;
            int mem2;
            int mem3;
            int mem4;
        };
        int elements[4];
    };
};

foo thefoo;
for (int i = 0; i < 4; ++i) {
    thefoo.elements[i] = i;
}

Es posible que esto no funcione en algunos compiladores, en este caso, deberá nombrar explícitamente la unión y la estructura dentro foo,

Aquí hay otro enfoque; Nunca tuve una razón para hacer esto, pero tiene la ventaja de no estropear la definición de la estructura. Cree una matriz de punteros a los miembros que le interesan y luego itere sobre esa matriz:

typedef struct { int mem1; int mem2; int mem3, int mem4; } foo;
...
foo theStruct;
int *structMembers[4] = { &theStruct.mem1, &theStruct.mem2, 
                          &theStruct.mem3, &theStruct.mem4};
...
for (i = 0; i < 4; i++)
{
  printf("%d\n", *structMembers[i]);
}

De esta manera, no tiene que preocuparse por los problemas de alineación y puede ordenar arbitrariamente cómo desea iterar sobre los miembros (por ejemplo, puede ordenarlo para que la caminata sea “mem4, mem3, mem2, mem1”).

    int* tmp = &data->mem1 ;  // explicitly get the address of the first int member

Como usted dice, debe tener cuidado con la alineación y otros problemas de diseño de memoria. Deberías estar bien haciendo esto con intaunque

Pero me preguntaría cuán legible haría esto su código.

Por cierto,

    tmp += ( i * sizeof(foo) ) ;

No creo que haga lo que crees que hace, pero no estoy del todo seguro de lo que quieres que haga. Utilizar ++tmp; si quieres pasar al siguiente int miembro de la misma foo objeto.

Iterando sobre miembros de estructura del mismo tipo en C
Guillermo campana

Debería poder convertir el puntero foo en un puntero int.

Una solución más limpia sería declarar foo de la siguiente manera.

typedef struct
{
    int fooints[ 4 ] ;
} foo ;

Luego iterar sobre la matriz int.

for ( int i = 0 ; i < 4 ; i++ )
{
    printf( "%d\n" , *( foo->fooints + i ) ) ;
}

Puede continuar creando funciones miembro para acceder y/o manipular la matriz int.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad