views:

4659

answers:

10

Hi,

I was trying to create a pseudo super struct to print array of structs. My basic structures are as follows.

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

...

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

I created the below struct to print the array of above mentioned structures. I got dereferencing void pointer error while compiling the below code snippet.

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

My intention is to have a common function to print all the arrays. Please let me know the correct way of using it.

Appreciate the help in advance.

Edit: The parameter name is changed to cmncntin from cmncnt. Sorry it was typo error.

Thanks, Mathew Liju

A: 

C isn't my cup o'java, but I think your problem is that "void *cmncnt" should be CMNCNT *cmncnt.

Feel free to correct me now, C programmers, and tell me this is why java programmers can't have nice things.

argonide
It shouldn't be a CMNCNT * parameter; that would make the ugliness appear all over the code wherever the function is called. Using a void * at least conceals the ghastliness in the implementation of the function. (And it wasn't me that gave you a down-vote -- I don't do drive-by down-votes.)
Jonathan Leffler
A: 

On this line:

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

You are trying to declare a new variable called cmncnt, but a variable with this name already exists as a parameter to the function. You might want to use a different variable name to solve this.

Also you may want to pass a pointer to a CMNCNT to the function instead of a void pointer, because then the compiler will do the pointer arithmetic for you and you don't have to cast it. I don't see the point of passing a void pointer when all you do with it is cast it to a CMNCNT. (Which is not a very descriptive name for a data type, by the way.)

yjerem
Sorry it was a typo. The parameter is cmncntin.
Liju Mathew
+1  A: 

Change the function declaration to char * like so:

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

the void type does not assume any particular size whereas a char will assume a byte size.

Adam Pierce
When i update void * to char * compile throw warning "passing arg 1 of `printCommonStatistics' from incompatible pointer type"
Liju Mathew
Well, I guess you'll have to cast when you call the function eg. printCommonStatistics((char *)mydata, elements, elementsize) but its starting to get messy now. Why do you need to pass your structure as void* ?
Adam Pierce
void * can be type casted to any data type. The function need not bother whether the parameter is of char type or int type or custom type.
Liju Mathew
Yes, but you have not cast your void* to any type which is why the compiler is confused.
Adam Pierce
Although the use of 'char *' is part of the solution, the 'void *' is the better choice in the function interface.
Jonathan Leffler
+1  A: 

You can't do this:

cmncnt->_cnt[0]

if cmnct is a void pointer.

You have to specify the type. You may need to re-think your implementation.

Klathzazt
A: 

Your expression

(CMNCNT *)&cmncntin[ii*cmncnt_elmsize]

tries to take the address of cmncntin[ii*cmncnt_elmsize] and then cast that pointer to type (CMNCNT *). It can't get the address of cmncntin[ii*cmncnt_elmsize] because cmncntin has type void*.

Study C's operator precedences and insert parentheses where necessary.

Windows programmer
Accurate, up to a point, but not very helpful.
Jonathan Leffler
You're right. I made the mistake of explaining to the original poster why the compiler gave "dereferencing void pointer error" instead of "My intention is to have a common function to print all the arrays. Please let me know the correct way of using it." Did u send teh codes?
Windows programmer
+1  A: 

The type of void is deliberately left incomplete. From this, it follows you cannot dereference void pointers, and neither you can take the sizeof of it. This means you cannot use the subscript operator using it like an array.

The moment you assign something to a void pointer, any type information of the original pointed to type is lost, so you can only dereference if you first cast it back to the original pointer type.

First and the most important, you pass T10CNT* to the function, but you try to typecast (and dereference) that to CMNCNT* in your function. This is not valid and undefined behavior.

You need a function printCommonStatistics for each type of array elements. So, have a printCommonStatisticsInt, printCommonStatisticsLong, printCommonStatisticsChar which all differ by their first argument (one taking int*, the other taking long*, and so on). You might create them using macros, to avoid redundant code.

Passing the struct itself is not a good idea, since then you have to define a new function for each different size of the contained array within the struct (since they are all different types). So better pass the contained array directly (struct_array[0]._cnt, call the function for each index)

Johannes Schaub - litb
A: 

This line is kind of tortured, don'tcha think?

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

How about something more like

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

Or better yet, if cmncnt_elmsize = sizeof(CMNCNT)

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

That should also get rid of the warning, since you are no longer dereferencing a void *.

BTW: I'm not real sure why you are doing it this way, but if cmncnt_elmsize is sometimes not sizeof(CMNCNT), and can in fact vary from call to call, I'd suggest rethinking this design. I suppose there could be a good reason for it, but it looks really shaky to me. I can almost guarantee there is a better way to design things.

T.E.D.
CMNCNT *cmncnt = ((CMNCNT *)(cmncntin + (ii * cmncnt_elmsize));will still fail of cmncntin is void* of course :)
Johannes Schaub - litb
No it won't. Did you actually test that?
rq
Visual C++ emits "error C2036: 'void *' : unknown size" when you try to do pointer arithmetic with void pointers. GCC warns about this if you use -Wpointer-arith or -pedantic.
bk1e
+1  A: 

The function

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

Works for me.

The issue is that on the line commented "Ptr Line" the code adds a pointer to an integer. Since our pointer is a char * we move forward in memory sizeof(char) * ii * cmncnt_elemsize, which is what we want since a char is one byte. Your code tried to do an equivalent thing moving forward sizeof(void) * ii * cmncnt_elemsize, but void doesn't have a size, so the compiler gave you the error.

I'd change T10CNT and T20CNT to both use int or long instead of one with each. You're depending on sizeof(int) == sizeof(long)

David Norman
+4  A: 

I think your design is going to fail, but I am also unconvinced that the other answers I see fully deal with the deeper reasons why.

It appears that you are trying to use C to deal with generic types, something that always gets to be hairy. You can do it, if you are careful, but it isn't easy, and in this case, I doubt if it would be worthwhile.

Deeper Reason: Let's assume we get past the mere syntactic (or barely more than syntactic) issues. Your code shows that T10CNT contains 20 int and T20CNT contains 20 long. On modern 64-bit machines - other than under Win64 - sizeof(long) != sizeof(int). Therefore, the code inside your printing function should be distinguishing between dereferencing int arrays and long arrays. In C++, there's a rule that you should not try to treat arrays polymorphically, and this sort of thing is why. The CMNCNT type contains 3 long values; different from both the T10CNT and T20CNT structures in number, though the base type of the array matches T20CNT.

Style Recommendation: I strongly recommend avoiding leading underscores on names. In general, names beginning with underscore are reserved for the implementation to use, and to use as macros. Macros have no respect for scope; if the implementation defines a macro _cnt it would wreck your code. There are nuances to what names are reserved; I'm not about to go into those nuances. It is much simpler to think 'names starting with underscore are reserved', and it will steer you clear of trouble.

Style Suggestion: Your print function returns success unconditionally. That is not sensible; your function should return nothing, so that the caller does not have to test for success or failure (since it can never fail). A careful coder who observes that the function returns a status will always test the return status, and have error handling code. That code will never be executed, so it is dead, but it is hard for anyone (or the compiler) to determine that.

Surface Fix: Temporarily, we can assume that you can treat int and long as synonyms; but you must get out of the habit of thinking that they are synonyms, though. The void * argument is the correct way to say "this function takes a pointer of indeterminate type". However, inside the function, you need to convert from a void * to a specific type before you do indexing.

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

(I like the idea of a file stream called stout too. Suggestion: use cut'n'paste on real source code--it is safer! I'm generally use "sed 's/^/ /' file.c" to prepare code for cut'n'paste into an SO answer.)

What does that cast line do? I'm glad you asked...

  • The first operation is to convert the const void * into a const char *; this allows you to do byte-size operations on the address. In the days before Standard C, char * was used in place of void * as the universal addressing mechanism.
  • The next operation adds the correct number of bytes to get to the start of the ith element of the array of objects of size elemsize.
  • The second cast then tells the compiler "trust me - I know what I'm doing" and "treat this address as the address of a CMNCNT structure".

From there, the code is easy enough. Note that since the CMNCNT structure contains long value, I used %ld to tell the truth to fprintf().

Since you aren't about to modify the data in this function, it is not a bad idea to use the const qualifier as I did.

Note that if you are going to be faithful to sizeof(long) != sizeof(int), then you need two separate blocks of code (I'd suggest separate functions) to deal with the 'array of int' and 'array of long' structure types.

Jonathan Leffler
A: 

Point of Information: Internal Padding can really screw this up.

Consider struct { char c[6]; }; -- It has sizeof()=6. But if you had an array of these, each element might be padded out to an 8 byte alignment!

Certain assembly operations don't handle mis-aligned data gracefully. (For example, if an int spans two memory words.) (YES, I have been bitten by this before.)

.

Second: In the past, I've used variably sized arrays. (I was dumb back then...) It works if you are not changing type. (Or if you have a union of the types.)

E.g.:

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

Allocated as

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

(Though padding/alignment can still screw you up.)

.

Third: Consider:

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

It solves problems with knowing how to print out the data.

.

Fourth: You could just pass in the int/long array to your function rather than the structure. E.g:

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

Invoked via:

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

Or:

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

This works much better than hiding data in structs. As you add members to one of your struct's, the layout may change between your struct's and no longer be consistent. (Meaning the address of _cnt relative to the start of the struct may change for _T10CNT and not for _T20CNT. Fun debugging times there. A single struct with a union'ed _cnt payload would avoid this.)

E.g.:

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

.

Fifth: If you must use structs... C++, iostreams, and templating would be a lot cleaner to implement.

E.g.:

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

But that's probably not what you are looking for...

Mr.Ree
"Consider struct { char c[6]; }; -- It has sizeof()=6. But if you had an array of these, each element might be padded out to an 8 byte alignment!" -- no. The standard requires sizeof to count the same padding, therefore 8 in this case.
Windows programmer