Передача аргументов в общем случае в C - PullRequest
0 голосов
/ 11 января 2011

Это связано с домашним заданием, но кажется, что это общая проблема, поэтому возникает вопрос.

У меня есть две структуры

typedef struct Vector {
    Element **elements;
    int top;
    int max_size;
} Vector;
typedef void Element;

вектор состоит из элементов, которыеможет быть любого типа, примитивным или нет.

Использование этой функции для добавления значений в вектор (еще не записано).

void Add_To_Vector(Vector *old_vector, Element *element, int index) {
    // create a new vector. loop throught the elements in old vector and 
    // assign each to a newly created vector. return new vector.
}

В основной части

scanf("%d %d", &vector_value_int, &index);
Add_To_Vector(vector, vector_value_int, index);

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

  • назначение делает указатель из целого числа без приведения
  • приведение к указателю из целого числа другого размера

Как передать этот или любой тип аргумента в функцию, используя элемент?Я также совершенно растерялся относительно того, что означает «элемент typedef void».Хранит ли он данные?нужно ли выделение памяти?

Спасибо!

Ответы [ 4 ]

2 голосов
/ 12 января 2011

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

Вот определение типа данных void из стандарта языка C (черновик n1256 ):

6.2.5 Типы
...
19 Тип void содержит пустой набор значений;это неполный тип, который не может быть завершен.

IOW, void - это тип данных типа «ничего из вышеперечисленного»;у него нет связанных значений.Функция с типом void не имеет возвращаемого значения, и (несуществующий) результат выражения void не должен использоваться.

Как и любой другой неполный тип, вы можете создавать указатели на void.Они служат своего рода универсальным типом указателя;указатель void может использоваться для указания на объект любого другого типа:

6.3.2.3 Указатели

1 Указатель на void может быть преобразованк или от указателя на любой неполный или тип объекта.Указатель на любой неполный объект или тип объекта может быть преобразован в указатель на void и обратно;результат должен сравниваться равным исходному указателю.

Так, например, я могу использовать указатель void, чтобы указывать на int, или float, или массив char и т. Д.:

int x = 1;
double y = 2.0;
char *z = "three";

void *p[3];

p[0] = &x;
printf("address of x is %p\n", p[0]);
p[1] = &y;
printf("address of y is %p\n", p[1]);
p[2] = &z;
printf("address of z is %p\n", p[2]);

Вот гипотетическая карта памятипоказывая значения каждого из них (допустим, 32 бита для целых и плавающих значений и ASCII для символьных данных):

Item        Address      0x00  0x01  0x02  0x03
----        -------      ----  ----  ----  ----
"three"     0x00001000   0x74  0x68  0x72  0x65  // string literals are stored "somewhere else"
            0x00001004   0x65  0x00  xxxx  xxxx  // xxxx indicates any random value
            ...
   x        0x10008000   0x00  0x00  0x00  0x01
   y        0x10008004   0x40  0x00  0x00  0x00  // Binary representation of 2.0 on my system
   z        0x10008008   0x00  0x00  0x10  0x00  // z contains the address of the first element of the string literal
   p        0x1000800C   0x10  0x00  0x80  0x00
            0x10008010   0x10  0x00  0x80  0x04
            0x10008014   0x10  0x00  0x80  0x08

Как видите, значение, хранящееся в p[0] (адрес 0x10008010), является адресомx (0x10008000).Аналогично, p[1] хранит адрес y, а p[3] хранит адрес z.Это свойство указателей void дает нам возможность отделить информацию о типе от кода, хотя и не так просто, как шаблоны C ++ или обобщения Java.Таким образом, мы можем использовать массив указателей void для создания универсального контейнера, который может содержать объекты (технически, указатели на объекты) любого типа.

Примечание. До принятия стандарта 1989 года вы бы использовали char * в качестве "общего" типа указателя;однако вам бы пришлось явным образом привести значение указателя к целевому типу, что не нужно делать с указателями void (в C; это не так для C ++, для которого требуется приведение explcit).Это одна из причин, по которой вы все еще видите, как люди разыгрывают результат malloc/calloc/realloc;когда-то у вас было до.

Итак, давайте посмотрим на ваши структуры данных и код, но сначала без определения типа:

struct Vector {                               
  void **elements;                               
  int top;
  int max_size;
};

void **elements объявляет elements как указатель на указатель на void;в этом случае мы собираемся динамически выделить вектор из void указателей.Это похоже на наш массив p в примере выше.

void allocateVector(struct Vector *vector, int maxSize)
{
  vector->elements = malloc(sizeof *vector->elements * maxSize);
  if (vector->elements)
    vector->maxSize = maxSize;
}

Теперь у нас есть 1-й массив указателей на void, способный содержать maxSize элементов.Теперь мы можем написать вашу Add_To_Vector функцию:

void Add_To_Vector(struct Vector *vector, void *objectPtr, int index)
{
  if (vector->elements)
  {
    if (index < vector->maxSize)
    {
      vector->elements[index] = objectPtr;
    }
  }
}

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

while (!done)
{
  scanf("%d %d", &vector_value_int, &index);
  Add_To_Vector(&vector, &vector_value_int, index);
  ...
}

, мы будем продолжать добавлять тот же адрес (&vector_value_int) в массив, а это не то, что нам нужно.Мы бы хотели сделать копию того, что есть в vector_value_int, но опять же, мы не настроены на то, чтобы это сделать, потому что мы не знаем тип того, что objectPtr указываетк.

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

void *copyIntValue(void *objectPtr)
{
  int *copy = malloc(sizeof *copy);
  if (copy)
    *copy = *(int *) objectPtr;
  return copy;
}

void Add_To_Vector(struct Vector *vector, void *objectPtr, void *(*cpyFunc)(void *), int index)
{
  if (vector->elements)
  {
    if (index < vector->maxSize)
    {
      vector->elements[index] = cpyFunc(objectPtr);
    }
  }
}

...
while (!done)
{
  scanf("%d %d", &vector_value_int, &index);
  AddToVector(&vector, &vector_value_int, copyIntValue, index);
}

Мы устранили вторую проблему, но не первую;мы до сих пор не знаем, на какой тип указывает vector->elements[i].Так или иначе, эта информация должна быть сохранена как часть массива.

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

2 голосов
/ 11 января 2011

Тип void - это просто способ сказать: «у него нет типа».Таким образом, указатель void является указателем на часть памяти, не зная, что в ней.Элемент = нет значимого типа.

Теперь, чтобы передать указатель, указывающий на vector_value_int, вы должны использовать оператор &

Add_To_Vector(vector, &vector_value_int, index);

& variable = l-value (address of) variable.

Если у вас есть указатель и вы хотите получить содержимое памяти, на которую он указывает, вы должны использовать оператор *.

* pointer = значение, сохраненное в адресе, содержащемся в указателе.

Здесь, поскольку тип void, вы должны привести его к (int), чтобы интерпретировать его как целое число.

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

Практический пример:

void add_to_vec(Vector *oldvector, Element *element, int pos) {
     printf("%d\n", *((int*)element));
     //iterate over the oldvector->elements
     //and create the new one
}

int main() {
     Vector vec;
     int x = 3; // this should be done with malloc
     add_to_vec(&vec, (Element*)&x, 0);
}
1 голос
/ 11 января 2011

Начну с низу:

typedef void Element

создает новое имя (Элемент) для существующего типа данных (void). По сути, любое место в коде, где у вас есть «Элемент», по сути, заменяется на «void» перед компиляцией. Чтобы решить вашу проблему, вам нужно взять переменную vector_value_int (предположительно автоматическое / стековое хранилище) и использовать ее для создания нового целого числа (на основе кучи, управляемого вашим вектором), затем использовать указатель вновь созданное целое число, приведенное как Элемент *, для передачи в вашу функцию Add_To_Vector.

0 голосов
/ 11 января 2011
typedef Element void;

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

Пример

// manipuating myint requires no knowledge of the underlying type, 
// it's right there in the declaration.
int myint = 10;
myint += 5;

// manipulating data probably requires knowledge of the underlying type.
// note: it points to the same data as myint.
void *data = &myint;
// we need to cast it to a type (usually a pointer to the original type)
int *data_as_int = (int *) data;
*data_as_int += 5;

// myint is now 20.

Это означает, что вам нужно передать указатель void / Element на Add_To_Vector. Если вы внимательно посмотрите на ваш звонок по номеру Add_To_Vector, вы можете узнать, почему вы получаете ошибки. Сравните с вашим звонком на scanf.

Первое сообщение об ошибке также дает вам важный совет.

...