Решение для предупреждения “разыменования указателя `void *'” в структуре на C?

StackOverflow https://stackoverflow.com/questions/289329

  •  08-07-2019
  •  | 
  •  

Вопрос

Я пытался создать псевдо-суперструктуру для печати массива структур.Мои основные структуры заключаются в следующем.

/* Type 10 Count */
typedef struct _T10CNT
{
    int _cnt[20];
} T10CNT;

...

/* Type 20 Count */
typedef struct _T20CNT
{
    long _cnt[20];
} T20CNT;
...

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

typedef struct _CMNCNT
{
    long  _cnt[3];
} CMNCNT;

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    int ii;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];
        fprintf(stout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

T10CNT struct_array[10];
...
printCommonStatistics(struct_array, NELEM(struct_array), sizeof(struct_array[0]);
...

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

Заранее благодарен за помощь.

Редактировать:Имя параметра изменено с cmncnt на cmncntin.Извините, это была опечатка.

Спасибо, Мэтью Лиджу

Это было полезно?

Решение

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

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

Более глубокая Причина:Давайте предположим, что мы преодолели простые синтаксические (или едва ли более чем синтаксические) проблемы.Ваш код показывает, что T10CNT содержит 20 int и T20CNT содержит 20 long.На современных 64-разрядных компьютерах - кроме Win64 - sizeof(long) != sizeof(int).Следовательно, код внутри вашей функции печати должен различать разыменование int массивы и long массивы.В C ++ существует правило, согласно которому вы не должны пытаться обрабатывать массивы полиморфно, и вот почему.Тип CMNCNT содержит 3 long ценности;отличается от структур T10CNT и T20CNT по количеству, хотя базовый тип массива соответствует T20CNT.

Рекомендация по стилю:Я настоятельно рекомендую избегать подчеркивания в именах.Как правило, имена, начинающиеся с подчеркивания, зарезервированы для использования реализацией и в качестве макросов.Макросы не имеют никакого отношения к области видимости;если реализация определяет макрос _cnt, это разрушит ваш код.Есть нюансы к тому, какие имена зарезервированы;Я не собираюсь вдаваться в эти нюансы.Гораздо проще думать, что "имена, начинающиеся с подчеркивания, зарезервированы", и это избавит вас от неприятностей.

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

Поверхностная Фиксация:Временно мы можем предположить, что вы можете лечить int и long как синонимы;но тем не менее вы должны избавиться от привычки думать, что это синонимы.В void * аргумент - это правильный способ сказать "эта функция принимает указатель неопределенного типа".Однако внутри функции вам необходимо преобразовать из void * к определенному типу перед выполнением индексации.

typedef struct _CMNCNT
{
    long    count[3];
} CMNCNT;

static void printCommonStatistics(const void *data, size_t nelem, size_t elemsize)
{
    int i;
    for (i = 0; i < nelem; i++)
    {
        const CMNCNT *cmncnt = (const CMNCNT *)((const char *)data + (i * elemsize));
        fprintf(stdout,"STATISTICS_INP: %ld\n", cmncnt->count[0]);
        fprintf(stdout,"STATISTICS_OUT: %ld\n", cmncnt->count[1]); 
        fprintf(stdout,"STATISTICS_ERR: %ld\n", cmncnt->count[2]);
    }
}

(Мне нравится идея файлового потока, называемого stout слишком. Предложение:используйте cut'n'paste на реальном исходном коде - это безопаснее!Я обычно пользуюсь "sed 's/^/ /' file.c" чтобы подготовить код для cut'n'paste в ответ SO.)

Что делает эта линия приведения?Я рад, что ты спросил...

  • Первая операция заключается в преобразовании const void * в const char *;это позволяет вам выполнять операции с размером в байт над адресом.В дни, предшествовавшие стандарту C, char * был использован вместо void * как универсальный механизм адресации.
  • Следующая операция добавляет правильное количество байтов, чтобы перейти к началу iй элемент массива объектов размером elemsize.
  • Затем второе приведение сообщает компилятору "доверься мне - я знаю, что делаю" и "рассматривай этот адрес как адрес структуры CMNCNT".

С этого момента код становится достаточно простым.Обратите внимание, что поскольку структура CMNCNT содержит long значение, которое я использовал %ld сказать правду, чтобы fprintf().

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

Обратите внимание, что если вы собираетесь быть верным sizeof(long) != sizeof(int), тогда вам понадобятся два отдельных блока кода (я бы предложил отдельные функции) для работы с 'массивом int' и 'массив long' типы структур.

Другие советы

Тип пустоты намеренно оставлен неполным.Из этого следует, что вы не можете разыменовывать указатели void, и вы также не можете использовать их размер.Это означает, что вы не можете использовать оператор подстрочного индекса, используя его как массив.

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

Первое и самое важное, вы проходите T10CNT* к функции, но вы пытаетесь ввести тип (и разыменовать), чтобы CMNCNT* в вашей функции.Это недопустимое и неопределенное поведение.

Вам нужна функция Printcommon Statistics для каждого типа элементов массива.Итак, имейте printCommonStatisticsInt, printCommonStatisticsLong, printCommonStatisticsChar которые все отличаются своим первым аргументом (один из которых принимает int*, другой принимая long*, и так далее).Вы могли бы создавать их с помощью макросов, чтобы избежать избыточного кода.

Передача самой структуры не является хорошей идеей, так как тогда вам придется определять новую функцию для каждого различного размера содержащегося массива внутри структуры (поскольку все они разных типов).Так что лучше передайте содержащийся массив напрямую (struct_array[0]._cnt, вызовите функцию для каждого индекса)

Измените объявление функции на char * вот так:

static int printCommonStatistics(char *cmncnt, int cmncnt_nelem, int cmncnt_elmsize)

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

Ты не можешь этого сделать:

cmncnt->_cnt[0]

если cmnct является указателем void.

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

Функция

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    char *cmncntinBytes;
    int ii;

    cmncntinBytes = (char *) cmncntin;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)(cmncntinBytes + ii*cmncnt_elmsize);  /* Ptr Line */
        fprintf(stdout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stdout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stdout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

Работает на меня.

Проблема в том, что в строке с комментарием "Ptr Line" код добавляет указатель на целое число.Поскольку наш указатель является символом *, мы перемещаемся вперед в памяти sizeof (char) * ii * cmncnt_elemsize , что нам и нужно, поскольку символ равен одному байту.Ваш код пытался сделать эквивалентную вещь, перемещая вперед sizeof(void) * ii * cmncnt_elemsize , но у void нет размера, поэтому компилятор выдал вам ошибку.

Я бы изменил T10CNT и T20CNT на использование int или long вместо одного для каждого.Вы зависите от sizeof(int) == sizeof(long)

На этой линии:

CMNCNT *cmncnt = (CMNCNT *)&cmncnt[ii*cmncnt_elmsize];

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

Также вы можете захотеть передать функции указатель на CMNCNT вместо указателя void, потому что тогда компилятор выполнит арифметику указателя за вас, и вам не придется его приводить.Я не вижу смысла передавать указатель void, когда все, что вы делаете с ним, - это преобразуете его в CMNCNT.(Кстати, это не очень описательное название для типа данных.)

Твое выражение лица

(CMNCNT *)&cmncntin[ii*cmncnt_elmsize]

пытается взять адрес cmncntin[ii*cmncnt_elmsize] и затем преобразовать этот указатель в type (CMNCNT *).Он не может получить адрес cmncntin[ii*cmncnt_elmsize], потому что cmncntin имеет тип void * .

Изучите прецеденты операторов языка Си и при необходимости вставьте круглые скобки.

Информационный пункт:Внутренняя прокладка действительно может все испортить.

Рассмотрим struct { char c[6];};-- Он имеет sizeof()=6.Но если бы у вас был такой массив, каждый элемент мог бы быть дополнен выравниванием в 8 байт!

Некоторые операции сборки не позволяют корректно обрабатывать неправильно выровненные данные.(Например, если значение int охватывает два слова памяти.) (ДА, меня это уже раздражало раньше.)

.

Второй:В прошлом я использовал массивы разного размера.(Тогда я был тупым ...) Это работает, если вы не меняете тип.(Или если у вас есть объединение типов.)

Например.:

struct T { int sizeOfArray;  int data[1]; };

Распределенный как

T * t = (T *) malloc( sizeof(T) + sizeof(int)*(NUMBER-1) );
                      t->sizeOfArray = NUMBER;

(Хотя заполнение / выравнивание все еще может вас испортить.)

.

Третий:Рассмотреть:

   struct T {
     int sizeOfArray;
     enum FOO arrayType;
     union U { short s; int i; long l; float f; double d; } data [1];
    };

Это решает проблемы со знанием того, как распечатать данные.

.

Четвертый:Вы могли бы просто передать массив int / long своей функции, а не структуре.Например,:

void printCommonStatistics( int * data, int count )
{
  for( int i=0;  i<count;  i++ )
    cout << "FOO: " << data[i] << endl;
}

Вызывается через:

_T10CNT  foo;
printCommonStatistics( foo._cnt, 20 );

Или:

 int a[10], b[20], c[30];
printCommonStatistics( a, 10 );
printCommonStatistics( b, 20 );
printCommonStatistics( c, 30 );

Это работает намного лучше, чем скрывать данные в структурах.Когда вы добавляете элементы в одну из ваших структур, макет может меняться в зависимости от вашей структуры и больше не быть согласованным.(Это означает, что адрес _cnt относительно начала структуры может измениться для _T10CNT, а не для _T20CNT.Веселые там времена для отладки.Единственная структура с полезной нагрузкой union'ed _cnt позволила бы избежать этого.)

Например.:

struct FOO {
  union {
         int     bar  [10];
          long biff [20];
   } u;
}

.

Пятый:Если вы должны использовать структуры...C ++, iostreams и шаблоны были бы намного проще в реализации.

Например.:

template<class TYPE> void printCommonStatistics( TYPE & mystruct, int count )
{
  for( int i=0;  i<count;  i++ )
    cout << "FOO: " << mystruct._cnt[i] << endl;
}      /* Assumes all mystruct's have a "_cnt" member. */

Но это, вероятно, не то, что вы ищете...

C - это не моя чашка o'java, но я думаю, ваша проблема в том, что "void * cmncnt" должно быть CMNCNT * cmncnt .

Не стесняйтесь поправлять меня сейчас, программисты на C, и скажите мне, почему у java-программистов не может быть хороших вещей.

Эта реплика какая-то вымученная, тебе не кажется?

CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];

Как насчет чего-то более похожего

CMNCNT *cmncnt = ((CMNCNT *)(cmncntin + (ii * cmncnt_elmsize));

Или еще лучше, если cmncnt_elmsize = sizeof(CMNCNT)

CMNCNT *cmncnt = ((CMNCNT *)cmncntin) + ii;

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

КСТАТИ:Я не совсем уверен, почему вы делаете это таким образом, но если cmncnt_elmsize иногда не равен sizeof(CMNCNT) и фактически может варьироваться от вызова к вызову, я бы предложил пересмотреть этот дизайн.Я полагаю, для этого могла быть веская причина, но мне это кажется действительно шатким.Я могу почти гарантировать, что есть лучший способ создавать вещи.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top