объединение по сравнению с пустым указателем

Каковы были бы различия между использованием просто пустоты* в противоположность объединению? Пример:

struct my_struct {
    short datatype;
    void *data;
}

struct my_struct {
    short datatype;
    union {
        char* c;
        int* i;
        long* l;
    };
};

Оба из тех могут использоваться для выполнения той же самой вещи, лучше использовать объединение или пустоту* хотя?

12
задан timrau 14 August 2012 в 23:48
поделиться

11 ответов

У меня в библиотеке был именно такой чехол. У нас был общий модуль сопоставления строк, который мог использовать разные размеры для индекса, 8, 16 или 32 бит (по историческим причинам). Итак, код был заполнен таким кодом:

if(map->idxSiz == 1) 
   return ((BYTE *)map->idx)[Pos] = ...whatever
else
   if(map->idxSiz == 2) 
     return ((WORD *)map->idx)[Pos] = ...whatever
   else
     return ((LONG *)map->idx)[Pos] = ...whatever

Было 100 таких строк. На первом этапе я изменил это объединение и обнаружил, что он более читабелен.

switch(map->idxSiz) {
  case 1: return map->idx.u8[Pos] = ...whatever
  case 2: return map->idx.u16[Pos] = ...whatever
  case 3: return map->idx.u32[Pos] = ...whatever
}

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

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

Изменить : добавлен комментарий, поскольку комментарии не форматируют код:

Изменение to switch приходилось раньше (теперь это настоящий код, как и был)

switch(this->IdxSiz) { 
  case 2: ((uint16_t*)this->iSort)[Pos-1] = (uint16_t)this->header.nUz; break; 
  case 4: ((uint32_t*)this->iSort)[Pos-1] = this->header.nUz; break; 
}

был изменен на

switch(this->IdxSiz) { 
  case 2: this->iSort.u16[Pos-1] = this->header.nUz; break; 
  case 4: this->iSort.u32[Pos-1] = this->header.nUz; break; 
}

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

16
ответ дан 2 December 2019 в 03:06
поделиться

На мой взгляд, указатель void и явное приведение типов - лучший способ, потому что для каждого опытного программиста на Си очевидно, какова цель.

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

11
ответ дан 2 December 2019 в 03:06
поделиться

Обычно объединение используется для хранения реальных объектов, а не указателей.

Я думаю, что большинство разработчиков C, которых я уважаю, не стали бы объединять разные указатели вместе; если требуется указатель общего назначения, просто использование void * определенно является «способом на языке Си». Язык жертвует безопасностью, чтобы позволить вам преднамеренно присваивать типы вещей; учитывая, сколько мы заплатили за эту функцию, мы могли бы также использовать ее, когда она упрощает код. Вот почему всегда были возможности избежать строгой типизации.

7
ответ дан 2 December 2019 в 03:06
поделиться

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

Изменить: Поскольку, похоже, есть некоторое недопонимание относительно того, как использовать неизвестный тип данных: в большинстве случаев вы предоставляете какой-то вид функции «регистрация». В типичном случае вы передаете указатели на функции, которые могут выполнять все необходимые операции с сохраняемым элементом. Он генерирует и возвращает новый индекс, который будет использоваться для значения, идентифицирующего тип. Затем, когда вы хотите сохранить объект этого типа, вы устанавливаете его идентификатор в значение, полученное вами после регистрации, и когда код, который работает с объектами, должен что-то сделать с этим объектом, он вызывает соответствующую функцию через указатель, который вы передали. В типичном случае эти указатели на функции будут в структуре , и она просто сохранит (указатели на) эти структуры в массиве. Значение идентификатора, которое он возвращает при регистрации, является просто индексом в массиве тех структур, где он сохранил эту конкретную.

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

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

5
ответ дан 2 December 2019 в 03:06
поделиться

Хотя использование union в настоящее время не является распространенным явлением, поскольку union является более определенным для вашего сценария использования, он хорошо подходит. В первом примере кода непонятно содержание данных.

2
ответ дан 2 December 2019 в 03:06
поделиться

Я бы предпочел пойти по маршруту объединения. Приведение из void * - это тупой инструмент, и доступ к данным с помощью правильно набранного указателя дает дополнительную безопасность.

2
ответ дан 2 December 2019 в 03:06
поделиться

Подбросьте монету. Union чаще используется с типами без указателей, поэтому здесь это выглядит немного странно. Однако явная спецификация типа, которую он предоставляет, является достойной неявной документацией. void * подойдет, если вы всегда будете знать, что будете обращаться только к указателям. Не начинайте вставлять здесь целые числа и полагаться на sizeof (void *) == sizeof (int).

Я не думаю, что в конечном итоге у любого из этих способов есть какое-либо преимущество перед другим.

2
ответ дан 2 December 2019 в 03:06
поделиться

В вашем примере это немного скрыто, потому что вы используете указатели и, следовательно, косвенное обращение. Но union определенно имеет свои преимущества.

Представьте:

struct my_struct {
   short datatype;
   union {
       char c;
       int i;
       long l;
   };
};

Теперь вам не нужно беспокоиться о том, откуда берется распределение для части значения. Никакого отдельного malloc () или чего-то подобного. И вы можете обнаружить, что доступ к -> c , -> i и -> l немного быстрее. (Хотя это может иметь значение, только если таких обращений много.)

2
ответ дан 2 December 2019 в 03:06
поделиться

It really depends on the problem you're trying to solve. Without that context it's really impossible to evaluate which would be better.

For example, if you're trying to build a generic container like a list or a queue that can handle arbitrary data types, then the void pointer approach is preferable. OTOH, if you're limiting yourself to a small set of primitive data types, then the union approach can save you some time and effort.

2
ответ дан 2 December 2019 в 03:06
поделиться

Если вы собираете свой код с -fstrict-aliasing (gcc) или аналогичными параметрами в других компиляторах, то вы должны быть очень осторожны с тем, как вы выполняете преобразование. Вы можете приводить указатель сколько угодно, но когда вы его разыменовываете, тип указателя, который вы используете для разыменования, должен соответствовать исходному типу (за некоторыми исключениями). Например, вы не можете сделать что-то вроде:

void foo(void * p)
{
   short * pSubSetOfInt = (short *)p ;
   *pSubSetOfInt = 0xFFFF ;
}

void goo()
{
   int intValue = 0 ;

   foo( &intValue ) ;

   printf( "0x%X\n", intValue ) ;
}

Не удивляйтесь, если это напечатает 0 (скажем) вместо 0xFFFF или 0xFFFF0000, как вы могли ожидать при построении с оптимизацией. Один из способов заставить этот код работать - это сделать то же самое с помощью объединения, и этот код, вероятно, также будет легче понять.

2
ответ дан 2 December 2019 в 03:06
поделиться

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

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

struct m1 {
   union {
    char c[100];
   };
};

struct m2 {
    void * c;
 };


 int
 main()
 {
printf("sizeof m1 is %d ",sizeof(struct m1));
printf("sizeof m2 is %d",sizeof(struct m2));
exit(EXIT_SUCCESS);
 }

Выход: sizeof m1 - 100 sizeof m2 - 4

EDIT : предполагая, что вы используете только указатели того же размера, что и void *, я думаю, что объединение лучше, так как вы получите небольшое обнаружение ошибок при попытке установить. c с целочисленным указателем и т. д. '. void *, если вы не создаете собственный распределитель, определенно быстро и грязно, к лучшему или к худшему.

1
ответ дан 2 December 2019 в 03:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: