Как хранить дабл в пустоте * в C - PullRequest
0 голосов
/ 11 февраля 2019

Я нашел Преобразование double в void * в C , но я не могу запустить ни одно из решений.Я хочу создать структуру с указателем void для хранения различных типов данных.

struct Table {
    int id;
    void *val;
}; 

В основной функции я присваиваю значения типа

struct Table t;

int i = 1;
double d = 2.5;

double *pd = &d;
void **p = (void**)pd;
void *dp = *p;

t.id = i;
t.val = dp;

, но не могу получить значениедвойника ..

printf("%d %f", t.id, t.val);

Спасибо.

Ответы [ 3 ]

0 голосов
/ 11 февраля 2019

Чтобы напечатать значение, попробуйте

printf("%d %f", t.id, *(double*)t.val);

t.val - это указатель, а не само значение, но так как это void*, вы не можете разыменовать его без приведения к правильному типу, которыйв вашем простом примере это (double*).

Для хранения данных вам не нужны три указателя.Вы можете просто назначить адрес:

t.id = i;
t.val = &d;

При таком подходе возможны две проблемы.

  • Если вы хотите «хранить разные типы данных», у вас естьзнать, какой тип хранится в вашем struct Table, когда вы хотите использовать (напечатать) значение, так как вы должны использовать приведение значения.(Возможно, вам нужно дополнительное поле type в вашей структуре.)

  • Если вы используете переменную с автоматическим или статическим хранением (как double d = 2.5; в примере) и сохраняете ее адресв t.val вы должны убедиться, что переменная не становится недействительной и что вы не измените ее значение позже.(Возможно, вам нужно динамическое выделение памяти для создания копии переменной.)

0 голосов
/ 11 февраля 2019

Несколько баллов:

  1. Вам не нужен посредник pd, чтобы назначить адрес d для t.val.Следующее должно работать идеально:
    t.val = &d;
    C позволяет прямое назначение между void * и другими типами указателей.C ++ этого не делает, поэтому это не будет работать в C ++ без приведения:
    t.val = (void *) &d;
  2. Однако это предполагает, что вы будете использовать t.val в течение времени жизни d.Если существует вероятность того, что d выйдет из области видимости, прежде чем использовать t.val, вам следует выделить новый блок памяти для хранения значения d:
    t.val = malloc( sizeof d );
    if ( t.val )
      *(double *)t.val = d;
    
    Таким образом, вместо сохранения адресаd в t.val, вы сохраняете адрес динамически выделяемого блока памяти и копируете значение d в этот динамический блок.Поскольку t.val является указателем на void, вам сначала нужно использовать выражение приведения (double *), чтобы сообщить компилятору, что он должен обращаться с ним как с указателем на double.Затем вам нужно разыменовать этот указатель с помощью унарного оператора *, чтобы присвоить значение d этому новому блоку памяти.Это также означает, что вам нужно будет de выделить этот блок, когда вы закончите с ним:
    free( t.val );
  3. В любом случае, вы печатаете значение, на которое t.val указывает следующее:
    printf( "%d %f\n", t.id, *(double *)t.val );
    Опять же, мы должны указать компилятору трактовать t.val как указатель на double и использовать унарный оператор * для получения значения, на которое указывает указатель.

РЕДАКТИРОВАТЬ

Вот очень надуманный пример того, что я имею в виду d "выход за рамки".Давайте предположим, что мы создаем объект t в main, затем вызываем одну функцию, которая назначает t.val, используя локальную переменную в этой функции, а затем вызываем другую функцию, чтобы вывести значение, на которое указывает t.val:

/**
 * Sets the val member of a struct Table object. Since we want to 
 * modify the contents of the parameter, we need to pass a pointer
 * to it.
 */
void assignVal( struct Table *tbl ) 
{
  double d = some_value();        // d only exists for the duration of this function
  ...
  tbl->val = malloc( sizeof d );  // since tbl is a pointer, use ->
  if ( tbl->val )                 // instead of . to access members
    *(double *) tbl->val = d;
  else
    // error could not allocate memory for tbl->val
  ...
}

void printVal( struct Table tbl )
{
  ...
  if ( tbl.val )
    printf( "%d %f\n", tbl->id, *(double *) tbl.val );
  else
    // tbl.val was never allocated
  ...
}

int main( void )
{
  struct Table t;
  assignVal( &t );
  printVal( t );
  if ( t.val )
    free( t.val );
}

Поскольку мы хотим, чтобы assignVal изменил содержимое t, нам нужно передать указатель на него в качестве аргумента.Синтаксис tbl->val такой же, как и (*tbl).val - мы разыменовываем tbl перед попыткой доступа к члену val.

Поскольку d перестает существовать, как только выходит assignVal, нам нужно выделить новый объект для хранения значения d.Как только мы закончим с этим значением, мы освобождаем t.val.

. В этом примере assignVal и printVal оба предполагают, что t.val указывает на объект double.Для более общего кода вы можете добавить еще один элемент, который отслеживает тип объекта, на который указывает val.

Например:

struct Table {
  int id;
  int valueType;
  void *val;
};

void assignDblVal( struct Table *tbl )
{
  double d = ...;
  ...
  t->valueType = DOUBLE; // where DOUBLE is some kind of integer constant
  t->val = malloc( sizeof d );
  if ( t->val )
    *(double *) t->val = d;
  ...
}

void assignIntVal( struct Table *tbl )
{
  int x = ...;
  ...
  tbl->valueType = INT;
  tbl->val = malloc( sizeof x );
  if ( tbl->val )
    *(int *) tbl->val = x;
  ...
}

void printVal( struct Table tbl )
{
  switch( tbl.valueType )
  {
    case CHAR:
      printf( "%d '%c'\n", tbl.id, *(char *) tbl.val );
      break;

    case INT:
      printf( "%d %d\n", tbl.id, *(int *) tbl.val );
      break;

    ...

    case DOUBLE:
      printf( "%d %f\n", tbl.id, *(double *) tbl.val );
      break;

    ...
  }
}

Таким образом, printVal не должен предполагать, что tbl.val указывает на двойное число - мы сообщаем ему, на какой тип он указывает, и он использует правильный спецификатор приведения и форматирования для печати. ​​

Помните,это быстрые и грязные примеры.Есть много более умных способов борьбы с полиморфизмом типов в C. Я просто не могу думать об одном прямо сейчас.

0 голосов
/ 11 февраля 2019

Размер двойного составляет 64 бита, в то время как указатель void составляет 32 бита в 32-битной программе и 64 бита в 64-битной программе.

Таким образом, вы можете сделать это в 64-битном исполняемом файле, но не в 32-битном.

Если вы хотите хранить различные типы данных в такой структуре, вы можете использоватьобъединение, которое содержит каждый из ваших типов данных.См https://www.tutorialspoint.com/cprogramming/c_unions.htm.

...