Решение для "разыменования` void * 'указатель "предупреждение в структуре в C? - PullRequest
2 голосов
/ 14 ноября 2008

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

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

...

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

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

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]);
...

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

Заранее признателен за помощь.

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

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

Ответы [ 10 ]

5 голосов
/ 15 ноября 2008

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

Похоже, что вы пытаетесь использовать 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 "чтобы подготовить код для вырезки и вставки в SO-ответ.)

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

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

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

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

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

2 голосов
/ 14 ноября 2008

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

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

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

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

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

1 голос
/ 15 ноября 2008

Функция

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)

1 голос
/ 14 ноября 2008

Вы не можете сделать это:

cmncnt->_cnt[0]

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

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

1 голос
/ 14 ноября 2008

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

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

тип void не принимает какого-либо определенного размера, тогда как символ принимает размер байта.

0 голосов
/ 15 ноября 2008

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

Рассмотрим 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. Веселое время отладки там. Единственная структура с полезной нагрузкой _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. */

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

0 голосов
/ 14 ноября 2008

Эта линия отчасти замучена, не правда ли?

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

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

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

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

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

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

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

0 голосов
/ 14 ноября 2008

Ваше выражение лица

(CMNCNT *)&cmncntin[ii*cmncnt_elmsize]

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

Изучите операторные приоритеты C. При необходимости вставьте скобки.

0 голосов
/ 14 ноября 2008

На этой строке:

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

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

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

0 голосов
/ 14 ноября 2008

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

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

...