开发者

Passing arguments generically in C

This is related to a homework assignment, but it seems like a common problem, hence the question.

I have two structs

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

a vector is comprised of elements, which can be of any type,primitive or not.

Using this function to add values to the vector (haven't written it yet).

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.
}

In the main part

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

Instead of passing vector_value_int to the function I need to pass an element pointer (somehow point element to the value entered). I've tried many different things but gcc keeps throwing warnings:

  • assignment makes pointer from integer without a cast
  • cast to pointer from integer of different size

How do I pass that or any type argument into the function using elem开发者_如何学Goent? I'm also quite lost as to what "typedef void Element" means. Does it store data? does it need memory allocation?

Thanks!


First, let's ignore all the typedefs; this is one of those rare cases where they obscure more than they illuminate.

Here's the definition of the void data type from the C language standard (draft n1256):

6.2.5 Types
...
19 The void type comprises an empty set of values; it is an incomplete type that cannot be completed.

IOW, void is sort of a "none-of-the-above" data type; it has no associated values. A function typed void has no return value, and the (nonexistant) result of a void expression should not be used.

Like any other incomplete type, you can create pointers to void. They serve as kind of a generic pointer type; a void pointer can be used to point to an object of any other type:

6.3.2.3 Pointers

1 A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

So, for example, I can use a void pointer to point to an int, or a float, or an array of char, etc.:

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

Here's a hypothetical memory map showing the values of each (assume 32 bits for ints and floats and ASCII for character data):

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

As you can see, the value stored at p[0] (address 0x10008010) is the address of x (0x10008000). Similarly, p[1] stores the address of y and p[3] stores the address of z. This property of void pointers gives us a way to separate type information from code, although not in as straightforward a way as C++ templates or Java generics. Thus, we can use an array of void pointers to build a generic container that can hold objects (technically, pointers to objects) of any type.

Note: Before the 1989 standard was adopted, you would have used a char * as a "generic" pointer type; however, you would have had to explicitly cast the pointer value to the target type, which you don't have to do with void pointers (in C; this is not true for C++, which does require the explcit cast). This is part of the reason you still see people casting the result of malloc/calloc/realloc; once upon a time, you had to.

So let's look at your data structures and code, but without the typedefs at first:

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

void **elements declares elements as a pointer to a pointer to void; in this case, we're going to dynamically allocate a vector of void pointers. This is similar to our array p in the example above.

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

We now have a 1-d array of pointers to void, capable of holding maxSize elements. Now we can write your Add_To_Vector function:

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

There are, however, two really big problems. First of all, we can't determine the type of the object being pointed to from the pointer itself; it's just an address, with no information describing the object being pointed to. If we wanted to, say, print the values of all the objects in the vector, we wouldn't know which conversion specifier to use. Secondly, if we structure our code as follows:

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

we'd keep adding the same address (&vector_value_int) to the array, which isn't what we want. We'd want to make a copy of what's in vector_value_int, but again, we're not set up to know how to do that because we don't know the type of what objectPtr points to.

One common way out of this problem is to pass along what's known as a callback function with the pointer information; the callback knows how to make a copy of the pointed-to value. For example:

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);
}

We've eliminated the second problem, but not the first; we still have no idea what type vector->elements[i] points to. Somehow, that information needs to be preserved as part of the array.

I think I'm about to break the forum software at this point, so I'll leave that as an exercise for later.


The type void is just a way of saying: 'it has not a type'. So a void pointer is pointer to a part of the memory without knowing what's in it. So Element = no meaningful type.

Now, to pass the a pointer pointing to vector_value_int you should use the operator &

Add_To_Vector(vector, &vector_value_int, index);

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

When you have a pointer and you want to get the content of the memory it points to you should use the operator *.

*pointer = value stored in address contained in pointer.

Here as the type is void you should cast it to (int) to interpret it as an integer.

If you are going to store something in a structure vector_value_int should be allocated using malloc, because if not it, the variable will be stored in the stack and when the procedure where it was declared finishes, that address will be released and uncertain results can arise.

Practical example:

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);
}


I'll start at the bottom:

typedef void Element

creates a new name (Element) for an existing data type (void). Essentially, any place in the code where you have "Element" is, in effect, replaced with "void" before compilation. What you'll need to do to solve your problem is to take your (presumably automatic/stack storage) vector_value_int variable, and use it to create a new (heap-based, managed by your vector) integer, then use a pointer to that newly created integer, cast as an Element*, to pass into your Add_To_Vector function.


typedef Element void;

..is an alias for the "void" type, which is kind of no type at all. The function takes a pointer to an Element, in effect a void pointer. Void pointers can point to basically anything but with the obvious caveat: you lose type information.

Example

// 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.

This means you need to pass a void/Element pointer to Add_To_Vector. If you look closely at your call to Add_To_Vector you may find why you are getting errors. Compare with your call to scanf.

The first error message also gives you a crucial hint.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜