Как перебрать структуру как массив в C: почему эта опция работает так, как работает? - PullRequest
2 голосов
/ 02 марта 2020

Недавно я нашел способ перебирать структуры, как если бы они были массивом (при условии, что тип значения одинаков во всех членах структуры), и он работает так, как я ожидаю и хочу этого. Однако у меня возникают проблемы с пониманием , почему это работает. Не понимая, почему это работает, я не чувствую себя комфортно, поэтому я надеялся, что сообщество сможет мне помочь. Ниже приведен некоторый код.

StructName - это обобщенное c имя для моей структуры многих значений с плавающей запятой, а iterate - обобщенное c имя функции. Пожалуйста, обратите внимание на отмеченную строку, так как это то, где я запутался в ее работе.

void iterate(StructName *s){
//some code
float *a;
a = (float*)&*s; //<---This line
//some more code
a++; //gets next struct member
}

Это работает, как и ожидалось, я могу перебирать свою структуру как массив. Если я сделаю следующее (удалите (float *) из отмеченной строки):

void iterate(StructName *s){
//some code
float *a;
a = &*s; //<---This line
//some more code
a++; //gets next struct member
}

, я получу следующее предупреждение:

warning: assignment to 'float *' from incompatible pointer type 'StructName *' {aka 'struct <anonymous> *'} [-Wincompatible-pointer-types]
     a = &*s;

Но код по-прежнему компилируется и запускается как ожидается.

И затем, если я сделаю следующее (уберите звездочку из отмеченной строки):

void iterate(StructName *s){
//some code
float *a;
a = (float*)&s; //<---This line
//some more code
a++; //gets next struct member
}

Моя индексация в значительной степени компенсируется, казалось бы, случайной величиной.

Ответы [ 2 ]

1 голос
/ 02 марта 2020

Итерация по элементам структуры - это не то, что можно сделать в C. Попытка сделать это вызывает неопределенное поведение. Из раздела 6.5.6 p8 рабочего проекта C11:

Когда выражение с целочисленным типом добавляется или вычитается из указателя, результат имеет тип операнда указателя. Если операнд-указатель указывает на элемент объекта массива, и массив достаточно велик, результат указывает на смещение элемента от исходного элемента, так что разность индексов результирующего и исходного элементов массива равна целочисленному выражению. Другими словами, если выражение P указывает на i-й элемент объекта массива, выражения (P) + N (эквивалентно, N + (P)) и (P) -N (где N имеет значение n) указывают соответственно i + n -ому и i-n-му элементам массива, если они существуют. Кроме того, если выражение P указывает на последний элемент объекта массива, выражение (P) +1 указывает один за последним элементом объекта массива, а если выражение Q указывает на один последний элемент последнего элемента массива, выражение (Q) -1 указывает на последний элемент объекта массива. Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один после последнего элемента объекта массива, при оценке не должно быть переполнения; в противном случае поведение не определено. Если результат указывает на один последний элемент массива, он не должен использоваться в качестве операнда оцениваемого унарного оператора *.

(выделение жирным шрифтом)

В этом случае взятие адреса члена с плавающей точкой массива можно рассматривать как массив размером 1.

Если вам нужна структура данных, которая содержит много чисел с плавающей запятой, которые можно индексировать как массив, используйте массив. Если вы выбрали структуру, потому что вам нужны удобные для чтения имена, которые вы получаете с членами структуры, вы можете использовать enum s для индексации массива с хорошими читаемыми именами.

Вот простой пример:

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

enum float_array_enum {FLOAT_X=0, FLOAT_Y, FLOAT_Z, FLOAT_ARR_LENGTH};

int main(void){
   float my_array[FLOAT_ARR_LENGTH] = {0.f};
   for(enum float_array_enum i = FLOAT_X; i < FLOAT_ARR_LENGTH; ++i) {
      my_array[i] = (float) i;
   }
   printf("my_array[FLOAT_X]: %f\n", my_array[FLOAT_X]);
   printf("my_array[FLOAT_Y]: %f\n", my_array[FLOAT_Y]);
   printf("my_array[FLOAT_Z]: %f\n", my_array[FLOAT_Z]);
   return EXIT_SUCCESS;
}
0 голосов
/ 02 марта 2020

Строка a = (float*)&*s; делает несколько вещей.

*s разыменовывает структуру. Затем &*s снова получает ссылку, поэтому &*s идентичен s, если s не является действительным указателем, и в этом случае преобразование завершится неудачно. Смысл &*s заключается в том, чтобы указатель указывал на реальную структуру.

Теперь s - это указатель на структуру, поэтому он в основном указывает на первый элемент структуры. Нам нужно привести это значение к (float*), чтобы компилятор знал, на какой размер указывает указатель, и как мы хотим с ним обращаться - так что все, что делает эта строка, это получает указатель на первый элемент структуры.

При увеличении a компилятор уже знает, что a является указателем с плавающей запятой, поэтому он увеличивает a на величину float, получая, таким образом, следующий элемент и так далее. Вот почему он будет работать только для структур с элементами того же типа (и даже это не гарантируется, так как стандарт C допускает заполнение между элементами).

Ваши два примера терпят неудачу, потому что в первом, вы не приводите указатель к float*, который создает присваивание разных типов, а во втором вы пытаетесь привести указатель к указателю на структуру к указателю, что не имеет смысла.

Это забавный эксперимент, однако, как уже упоминали другие, это нехорошая практика, и вам действительно не следует регулярно повторять подобные структуры.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...