Цикл по элементам структуры с использованием указателей в C - PullRequest
0 голосов
/ 17 октября 2018

Я написал этот код для перебора элементов структуры.Работает нормально.Могу ли я использовать подобный метод для структур со смешанными типами элементов, то есть некоторые целые числа, некоторые числа с плавающей запятой и ...?

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

struct newData
{
    int x;
    int y;
    int z;
}  ;

int main()
{
    struct newData data1;
    data1.x = 10;
    data1.y = 20;
    data1.z = 30;

    struct newData *data2 = &data1;
    long int *addr = data2;
    for (int i=0; i<3; i++)
    {
        printf("%d \n", *(addr+i));
    }
}

Ответы [ 5 ]

0 голосов
/ 18 октября 2018

Все хорошие ответы выше.Но есть еще одна вещь, которая опасна в вашем коде:

struct newData *data2 = &data1;
long int *addr = data2;

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

Все проблемы с разыменованием в структуру в стороне, вы могли бы использовать что-товот так:

struct newData *data2 = &data1;
void * addr = data2;

for(int i=0; i < 3; i++){
    printf("%d \n", *((long int *)addr+i));
}

Теперь это плохой код.Вы используете long int для компенсации заполнения, которое ваш компилятор поместил в вашу структуру;Я полагаю, что вы пришли к этому экспериментально.

Вы можете узнать о дополнении, если оно есть, компилятор применим к вашей структуре:

#include <assert.h>
.
.
.
assert(sizeof(struct newData) / sizeof(int) == 3);

Это по крайней мере завершит вашу программу, еслитам происходит что-то подозрительное, либо путем заполнения, либо потому, что ваша структура не соответствует 3-х элементной.Все еще плохой код.

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

(при условии, что вы вычислили некоторое значение заполнения между вашими (идентичными!) Членами структуры:

#include <assert.h>
.
.
.
//assert(sizeof(struct newData) / sizeof(int) == 3);

//Very ugly....don't really do this.
int padding = (sizeof(struct newData) / sizeof(int) / 3)  - 1;

.
.
.
struct newData *data2 = &data1;

// Use a void pointer, which can hold all other data pointers
void * addr = data2;

for(int i=0; i < 3; i++)
{
// Cast the pointer to (char*), because that is the only guaranteed
// type size - 1 byte
// Do your pointer arithmetic by using the actual size of int on your 
// machine, plus the padding

printf("%d \n", *((char *)addr + (i * (sizeof(int) + padding))));
}

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

PS: AFAIK, нет никакой гарантии, что память, занятая структурой, является смежной, независимо от проблем заполнения. Я предполагаю, что (маленькие) структуры в стеке являются непрерывными в большинстве случаев, но большие накуча вполне может быть разбросана по разным областям памяти.

Поэтому очень опасно в любой момент делать арифметику указателей в структуре.

0 голосов
/ 17 октября 2018

Короткий ответ - «нет».

Более длинный ответ: Ваш пример того, что «работает», на самом деле тоже не правомерен.Если по какой-либо причине вы действительно хотите иметь возможность циклически повторять несколько типов, вы можете проявить творческий подход со структурами и объединениями.Например, наличие структуры с одним членом, которая сообщает о типе данных, который содержит другой член.Другой член будет объединением всех возможных типов данных.Примерно так:

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

enum TYPE {INT, DOUBLE};

union some_union {
  int x;
  double y;
};

struct multi_type {
  enum TYPE type;
  union some_union u;
};

struct some_struct {
  struct multi_type array[2];
};

int main(void) {
   struct some_struct derp;

   derp.array[0].type = INT;
   derp.array[0].u.x = 5;
   derp.array[1].type = DOUBLE;
   derp.array[1].u.y = 5.5;

   for(int i = 0; i < 2; ++i) {
      switch (derp.array[i].type) {
         case INT:
            printf("Element %d is type 'int' with value %d\n", i, derp.array[i].u.x);
            break;
         case DOUBLE:
            printf("Element %d is type 'double' with value %lf\n", i, derp.array[i].u.y);
            break;
      }
   }
   return EXIT_SUCCESS;
}

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

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

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

enum TYPE {INT, DOUBLE};

struct multi_type {
  enum TYPE type;
  void *data;
};

struct some_struct {
  struct multi_type array[2];
};

int main(void) {
   struct some_struct derp;
   int x;
   double y;

   derp.array[0].type = INT;
   derp.array[0].data = &x;
   *(int *)(derp.array[0].data) = 5;
   derp.array[1].type = DOUBLE;
   derp.array[1].data = &y;
   *(double *)derp.array[1].data = 5.5;

   for(int i = 0; i < 2; ++i) {
      switch (derp.array[i].type) {
         case INT:
            printf("Element %d is type 'int' with value %d\n", i, *(int *)derp.array[i].data);
            break;
         case DOUBLE:
            printf("Element %d is type 'double' with value %lf\n", i, *(double *)derp.array[i].data);
            break;
      }
   }
   return EXIT_SUCCESS;
}

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

0 голосов
/ 17 октября 2018

Вы вызываете неопределенное поведение .Тот факт, что он работает, не означает, что он действителен.

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

Это подробно описано в разделе 6.5.6p8 стандарта C :

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

0 голосов
/ 17 октября 2018

Мало того, что вы не можете сделать это со смешанными типами, даже рассматриваемый код опрометчив.Ваш код

  • предполагает, что между членами нет заполнения.
  • имеет строгое нарушение псевдонимов (int и long не совместимы)
  • нетиметь явное приведение при назначении long int *addr = data2;
  • предполагает, что int и long имеют одинаковый размер (не так в 64-битной Linux)
  • имеет доступ к массиву за пределами границ: даже при приведении к указателю на первый член (int *addr = (int*)data;) выполнение addr[1] обращается к массиву вне границ.

TL; DR: в C "это работает" не означает, что этоверно.Поэтому, если ваша программа неудачна, не удивляйтесь, если когда-нибудь, где-то, где-то, когда вы меньше всего этого ожидаете, кто-то подходит к вам и говорит: улыбнитесь!У вас здесь неопределенное поведение.

0 голосов
/ 17 октября 2018

В C "все работает нормально" недостаточно.Поскольку вашему компилятору разрешено делать это:

struct newData
{
    int x;
    char padding1[523];
    int y;
    char padding2[364];
    int z;
    char padding3[251];
};

Конечно, это крайний пример.Но вы получите общую идею;не гарантируется, что ваш цикл будет работать, потому что не гарантируется, что struct newData эквивалентно int[3].

Так что нет, это невозможно в общем случае, потому что это не всегда возможно в конкретном случае!


Теперь вы можете подумать: «Какие идиоты решили это ?!»Ну, я не могу вам этого сказать, но я могу сказать вам, почему.Компьютеры сильно отличаются друг от друга, и если вы хотите, чтобы код работал быстро, компилятор должен иметь возможность выбирать, как скомпилировать код.Вот пример:

Процессор 8 имеет инструкцию для получения отдельных байтов и помещения их в регистр:

GETBYTE addr, reg

Это хорошо работает с этой структурой:

struct some_bytes {
   char age;
   char data;
   char stuff;
}

struct some_bytes может успешно занять 3 байта, и код работает быстро.Но как насчет процессора 16?Он не имеет GETBYTE, но имеет имеет GETWORD:

GETWORD even_addr, reghl

Он принимает только четный адрес и читает два байта;один в «верхнюю» часть регистра и один в «низкую» часть регистра.Чтобы сделать код быстрым, компилятор должен сделать следующее:

struct some_bytes {
   char age;
   char pad1;
   char data;
   char pad2;
   char stuff;
   char pad3;
}

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

Фактически, вы уже сталкивались с этим поведением!Ваш конкретный компилятор делал это:

struct newData
{
    int x;
    int pad1;
    int y;
    int pad2;
    int z;
    int pad3;
};

Поскольку ваш конкретный компилятор определяет long int как удвоенную длину int, вы смогли сделать это:

|  x  | pad |  y  | pad |  z  | pad |

| long no.1 | long no.2 | long no.3 |
| int |     | int |     | int |     

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

for (int i=0; i<3; i++)
{
    printf("%d \n", *(addr+i));
}

Хмм ... addr от data2 от data1, чтоэто указатель на struct newData.Спецификация C говорит, что только указатель на начало структуры будет разыменован, поэтому я могу предположить, что i всегда 0 в этом цикле!

for (int i=0; i<3 && i == 0; i++)
{
    printf("%d \n", *(addr+i));
}

Это означает, что он работает толькоодин раз!Ура!

printf("%d \n", *(addr + 0));

И все, что мне нужно для компиляции, это:

int main()
{
    printf("%d \n", 10);
}

Ух, программист будет так рад, что мне удалось так быстро ускорить этот код!

Вам не понравится.На самом деле, вы получите неожиданное поведение и не сможете понять, почему.Но вы были бы рады, если бы вы написали код, свободный от неопределенного поведения, и ваш компилятор сделал нечто подобное.Так и остается.

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