Итерация по членам структуры того же типа в C

Можно ли выполнить итерацию структуры C, где все элементы одного типа, с помощью указателя. Вот пример кода, который не компилируется:

#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 ;
}

Члены foo должны быть выровнены в памяти так, чтобы они шли друг за другом, при условии, что ваш компилятор/ядро не пытается обеспечить защиту стека от переполнения буфера.

Итак, мой вопрос:

Как бы я перебирал элементы структуры C одного типа.


person Misha M    schedule 08.12.2009    source источник
comment
Вы пробовали использовать гипс, например int * tmp = (int *)data'? Какое сообщение об ошибке вы получаете?   -  person David Thornley    schedule 09.12.2009
comment
Дэвид, после последнего обновления код работает. Это присваивание выдает предупреждение от компилятора. Я надеялся, что есть более элегантное решение.   -  person Misha M    schedule 09.12.2009
comment
В соответствии со спецификацией языка C не требуется, чтобы поля в структуре располагались рядом друг с другом в памяти; компиляторам разрешено вставлять заполнение между элементами поля. Если вам нужна итерация, используйте массив.   -  person Thomas Matthews    schedule 10.12.2009


Ответы (8)


Самый простой способ — создать объединение, одна часть которого содержит каждого члена по отдельности, а другая — массив. Я не уверен, что дополнение, зависящее от платформы, может помешать выравниванию.

person Mark Ransom    schedule 08.12.2009
comment
Спасибо за эту идею, совершенно забыл об использовании союза здесь. Меня беспокоят архитектурные зависимости, хотя все они 32-битные, так что все должно быть в порядке. Мое беспокойство при изменении определения структуры будет связано с влиянием на остальную часть кода, который ее использует. Необходимость перекомпилировать библиотеки и другие зависимости. - person Misha M; 09.12.2009
comment
Как указано ниже, хотя это будет работать со многими компиляторами и платформами, на других это может не сработать. Пожалуйста, сделайте одолжение вашим мейнтейнерам и напишите модульные тесты для этой итерации, чтобы, когда она когда-нибудь сломается в какой-нибудь странной системе, это было сразу видно. - person emk; 09.12.2009
comment
Согласен, по крайней мере что-то вроде assert((void*)&my_foo.s.mem4 == (void*)&my_foo.a.mem[3]); - person Mark Ransom; 09.12.2009

Большинство попыток использования объединения с массивом обречены на неудачу. У них есть неплохие шансы работать, пока вы используете только int, но для других, особенно меньших типов, они, вероятно, будут давать сбои довольно часто, потому что компилятор может (и особенно с меньшими типами часто будет) добавлять отступы между членами a struct, но не может делать это с элементами массива).

Однако в C есть макрос offsetof(), который вы можете использовать. Это дает смещение элемента в struct, поэтому вы можете создать массив смещений, а затем (с некоторой осторожностью в приведении) вы можете добавить это смещение к адресу структуры, чтобы получить адрес члена. Внимание при приведении заключается в том, что смещение находится в байтах, поэтому вам нужно привести адрес struct к char *, затем добавить смещение, а затем привести результат к типу члена (int в вашем случае).

person Jerry Coffin    schedule 08.12.2009
comment
Джерри, спасибо. Я исследую offsetof(). Это звучит как лучшее решение, чем использование союза. - person Misha M; 09.12.2009

С точки зрения языка: нельзя. элементы данных структуры не являются... э-э... "итерируемыми" в C, независимо от того, относятся ли они к одному типу или к разным типам.

Используйте массив вместо группы независимых членов.

person AnT    schedule 08.12.2009
comment
Список или карта на самом деле работали бы здесь лучше. Иногда невозможно изменить архитектуру кода, поэтому приходится довольствоваться тем, что есть. Я не согласен с вами относительно точки зрения языка. C, как определен язык, не волнует. - person Misha M; 09.12.2009
comment
@Misha: Язык, как определено, не заботится о том, есть ли у вас массив или структура. Вы можете перебирать массив, но не структуру. - person David Thornley; 09.12.2009

Вы также можете использовать безымянный союз/структуру:

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;
}

Это может не работать на некоторых компиляторах, в этом случае вам придется явно указать объединение и структуру внутри foo,

person Andreas Brinck    schedule 08.12.2009

Вот еще один подход; У меня никогда не было причин делать это, но это имеет то преимущество, что не искажает определение структуры. Создайте массив указателей на интересующие вас члены, а затем выполните итерацию по этому массиву:

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]);
}

Таким образом, вам не нужно беспокоиться о проблемах с выравниванием, и вы можете произвольно упорядочить, как вы хотите перебирать элементы (например, вы можете заказать так, чтобы прогулка была «mem4, mem3, mem2, mem1»).

person John Bode    schedule 08.12.2009

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

Как вы говорите, вы должны быть осторожны с выравниванием и другими проблемами с расположением памяти. Вы должны быть в порядке, делая это с ints.

Но я бы спросил, насколько читабельным это сделает ваш код.

Кстати,

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

Я не думаю, что это делает то, что вы думаете, но я не совсем уверен, что вы хотите. Используйте ++tmp;, если вы хотите перейти к следующему int элементу того же foo объекта.

person dave4420    schedule 08.12.2009
comment
Будьте осторожны, этот подход чувствителен к упаковке структуры. Я уверен, что вы можете заставить его работать, но я бы не советовал этого делать. - person Tim Sylvester; 09.12.2009
comment
Я пробовал этот подход ранее, но получить адрес первого участника тоже не получилось. Адрес, который он продолжал получать, был неправильным. - person Misha M; 09.12.2009
comment
Дэйв, ++tmp не будет работать для всех архитектур, поэтому используется sizeof. Хотя вы правы, это тоже не сработает, sizeof(foo) должен быть sizeof(int). Спасибо Исправлено в вопросе - person Misha M; 09.12.2009
comment
@Misha, еще раз, почему ++tmp не будет работать для всех архитектур? Я думаю, что он должен продвигать tmp на размер объекта, на который он указывает. Я считаю, что это часть стандарта C, если кто-то может указать на часть стандарта, которая определяет это, это поможет. - person Nick Meyer; 09.12.2009
comment
Ник, ты прав. Просто посмотрел, и ++tmp сделает то же самое. Спасибо - person Misha M; 09.12.2009
comment
Технически смещение между mem1 и mem2 не обязательно должно быть int. Между членами структуры могут быть отступы. На практике я не могу представить заполнение ints. - person David Thornley; 09.12.2009

Вы должны иметь возможность привести указатель foo к указателю int.

Более чистым решением было бы объявить foo следующим образом.

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

Затем выполните итерацию по массиву int.

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

Вы можете создать функции-члены для доступа и/или управления массивом int.

person William Bell    schedule 08.12.2009

Лучший способ, который я нашел для итерации структур, которые имеют элементы одного и того же типа данных;

Создайте перечисление с нужными именами. Добавьте последний элемент для ограничения итерации. Создайте структуру, содержащую массив размером TOTAL_STRUCT_MEMBERS. Затем повторите с for.

enum StructMembers{
   data1,
   data2,
   data3,
   TOTAL_STRUCT_MEMBERS
};

struct DataStruct{
   int datas[TOTAL_STRUCT_MEMBERS];
};

// iterating
for(int i = 0; i < TOTAL_STRUCT_MEMBERS; i++)
{
   DataStruct.datas[i] = 0;
}

// Direct access
DataStruct.datas[data2] = 25;
person omerbguclu    schedule 11.06.2021