2

Say I have a group of struct's that I want to manage arrays of via blocks of pointers. I would like to initialize these blocks in a generic fashion, and here's what I'm thinking:

enum my_type {STRUCT_1, STRUCT_2, STRUCT_3} ;

struct my_struct_1 {...} ;
struct my_struct_2 {...} ;
struct my_struct_3 {...} ;

void * my_array_init(my_type t) {
    void * array = NULL ;
    switch(t) {
    case STRUCT_whatever :
        (my_struct_whatever **) array ; /* does this cast or is assignment needed? */
    default :
    }
    array = malloc(sizeof(*array) * BLOCK_SIZE) ;
    if(array) {
        for(i=0 ; i<BLOCK_SIZE ; ++i)
            array[i] = NULL ;
    }

    return array ;
}

Then I was thinking I could use it like:

/* Initially set all pointers to NULL */
my_struct_1 ** s1 = NULL ;
my_struct_2 ** s2 = NULL ;
my_struct_3 ** s3 = NULL ;

s1 = my_array_init(STRUCT_1) ;
s2 = my_array_init(STRUCT_2) ;
s3 = my_array_init(STRUCT_3) ;

Am I on a right track, do I have the level of indirection wrong, or should I just bag it and write individual 'init' functions? Regarding 'void *', since it can point to anything I'd think it could be a handle (pointer to pointer), and I've seen conflicting arguments about 'void **'. However, 'void **' does appear in at least one of the examples in K&R 2nd ed. - in '5.11 Pointers to Functions' on page 119. For what it's worth, all the struct's contain only pointers and basic data types.

Peter
  • 113
  • 3
  • 8

4 Answers4

2

(my_struct_whatever **)array will not permanently change the type of a variable. You need an assignment, and in the case of a void pointer, you won't even need the cast:

void *vp;
foobar_t *fp;

fp = vp;  // the cast is implicit; leaving it out is considered good style
Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • No, because that would just assign `array` to itself; it'd be a no-op. You need an extra variable with a different type. Note that in the case of a `void` pointer, you can (and *should*) omit the explicit cast. – Fred Foo Jan 05 '12 at 21:37
  • Thanks. So could I just: `(my_struct_whatever **)array = array` since 'array' is a void pointer? Would this still be a no-op, or would it work since a change in type is being made to 'array'? Regarding explicit casting, I'm learning better, but since I'm mostly self-taught via K&R and it uses explicit casting from malloc, I've been doing it their way. – Peter Jan 05 '12 at 21:47
  • @Peter This is not C. The left operand of the assignment operator has to be a modifiable lvalue and the result of a cast it not a lvalue. – ouah Jan 05 '12 at 22:24
  • @ouah I guess I don't understand why it's not considered an lvalue, but you're certainly correct. I got confused because K&R says you can convert any value to type void * without loss of info, and I figured that casting to another type might be considered the same way as any other operation that also uses the lvalue as an rvalue (i.e x=x+y+z) Thanks for your help. – Peter Jan 06 '12 at 00:07
  • (deleted a comment from a moment ago, while I think this through further) – Aaron McDaid Jan 06 '12 at 00:19
  • @larsmans, I don't see how this answer helps to solve the original problem. -1 (reluctantly, as your statement is correct). – Aaron McDaid Jan 06 '12 at 01:21
  • @AaronMcDaid It answers the casting v. assignment question, which is certainly a help to me. – Peter Jan 06 '12 at 02:15
  • @AaronMcDaid: I was only trying to answer part of the question; the part in the comment. – Fred Foo Jan 06 '12 at 10:58
0

The calloc function initializes the allocated memory to zero, which is what you want, since you are initializing all the values to zero (NULL). So, all you need to do is something like:

#include <stdlib.h>

void * my_array_init(my_type t) {
    switch(t) {
    case STRUCT_1:
        return calloc(BLOCK_SIZE, sizeof(struct my_struct_1 *));
    case STRUCT_2:
        return calloc(BLOCK_SIZE, sizeof(struct my_struct_2 *));
    case STRUCT_3:
        return calloc(BLOCK_SIZE, sizeof(struct my_struct_3 *));
    default:
        return NULL;
    }
}
igorrs
  • 350
  • 1
  • 7
  • +1. But you could improve it further. All those `sizeof`s will be the same. The `sizeof` a pointer is the same for all pointer types. You could write the whole function as `return calloc(BLOCK_SIZE, sizeof(void*));` – Aaron McDaid Jan 06 '12 at 00:54
  • 1
    No, the C standard does not guarantee that a null pointer is represented as all-bits-zero. It is in most (current) implementations, but making that assumption in your code makes it potentially non-portable. – Keith Thompson Jan 06 '12 at 01:00
  • @Aaron Nice remark. I'm sorry I can't vote your answer up: I have just created my account, so I don't have enough reputation. – igorrs Jan 06 '12 at 01:04
  • 1
    Discussion about NULL vs. 0 (let's not reproduce it here in the comments): http://stackoverflow.com/questions/1296843/what-is-the-difference-between-null-0-and-0 – igorrs Jan 06 '12 at 01:13
  • good point, @KeithThompson. Doesn't C++ guarantee that it'll be equal to 0? Anyway, I hate to say this, but I think my answer is the only correct one now :-) . Am I right? – Aaron McDaid Jan 06 '12 at 01:19
  • 1
    @AaronMcDaid: No, C++ makes no such guarantee either. In both C and C++ the constant `0` is a *null pointer constant*, but that just means it evaluates to a null pointer when *converted* to a pointer type. The internal representation of a null pointer could be anything. – Keith Thompson Jan 06 '12 at 01:21
  • @AaronMcDaid Well... actually... http://stackoverflow.com/questions/1241205/are-all-data-pointers-of-the-same-size-in-one-platform ;-) – igorrs Jan 06 '12 at 04:42
  • I have edited my other answer to point out the two concerns that were raised here. – igorrs Jan 06 '12 at 05:11
  • 1
    Thanks @igorrs, recently I learned that bit could have more than 8 bits. Where will this madness end :-) – Aaron McDaid Jan 06 '12 at 14:07
0

In this particular example, the my_init_array function does not need to know the type of the struct. This is because it's creating an array of pointers (all NULL), and is not creating an array of the struct.

(Warning: Technically, it's possible that your platform is one where sizeof(void*) is not the same as sizeof(struct my_struct_1 *). See this answer. The good news is that pointers to all struct types will be the same size. If you are paranoid, you should edit this code accordingly. I have assumed, in this answer, that sizeof(void*)==sizeof(struct my_struct_WHATEVER *). You might be best to simply copy and paste the entire function three times and write one function for each of the three struct types. A macro might help here.)

#include<stdlib.h>

enum my_type {STRUCT_1, STRUCT_2, STRUCT_3} ;

struct my_struct_1 { int x; };
struct my_struct_2 { double x; };
struct my_struct_3 { char * x; };

#define BLOCK_SIZE 10

void * my_array_init(void) {
    void ** array = NULL ;
    array = malloc(sizeof(*array) * BLOCK_SIZE) ;
    if(array) {
        int i;
        for(i=0 ; i<BLOCK_SIZE ; ++i)
            array[i] = NULL ;
    }
    return array ;
}

int main() {
        struct my_struct_1 ** s1 = my_array_init();
        struct my_struct_2 ** s2 = my_array_init();
        struct my_struct_3 ** s3 = my_array_init();
}

I have made a lot of changes here to the code in the original question. I think the interesting line is:

array = malloc(sizeof(*array) * BLOCK_SIZE) ;

The sizeof(*array) is independent of the type of struct been using. array is a pointer-to-a-pointer and hence *array is simply a pointer. And the size of a pointer is always the same. Therefore this line doesn't need to depend in any way on the struct type. And array can be of type void**.

The only other interesting line is

array[i] = NULL ;

Again, array[i] is just a pointer, and we are setting it to NULL. And hence this code doesn't depend on the type of struct. It is this line which requirs that array be void**, not void*

Community
  • 1
  • 1
Aaron McDaid
  • 26,501
  • 9
  • 66
  • 88
  • Ultimately this, because it explains so well what's going on, why the code change, and answers the `void * v. void **` question. `void` confuses me; I imagine I'm not alone. I was also getting a bit lost in the level of indirection. Thanks, I think I got it. – Peter Jan 06 '12 at 02:18
  • Thanks, personally I'm not 100% satisfied with my answer here. I'd like to give a more detailed answer. I'm pretty sure what I've said is correct, but it would help if I said more about the difference between `void*` and `void**`. If array was `void*`, then `array[i]=NULL` wouldn't compile (any attempt to dereference a void* will fail to compile). But `void**` is OK on that line because `array[i]` is of type `void*`. But there's a lot more that I/we should say on that matter. – Aaron McDaid Jan 06 '12 at 02:46
  • I think I've got my mind wrapped around the `void * v. void **` issue; to put it simplistically, I need to use `void **` whenever I want to deal with a handle, and I can dereference so long as don't try to deref the actual `void *` itself. I already understand (I hope) the difference between pointers and handles; I have to remember to apply the same rules to `void` and not let it confuse me. – Peter Jan 06 '12 at 04:03
0

Taking my previous answer and Aaron McDaid's remarks into account, it turns out you don't even need the function my_array_init. Code along these lines will do:

#include <stdlib.h>

struct my_struct_1 {char a;} ;
struct my_struct_2 {short b;} ;
struct my_struct_3 {int c;} ;

#define BLOCK_SIZE 10

int main() {
    struct my_struct_1 ** s1 = calloc(BLOCK_SIZE, sizeof(void*));
    struct my_struct_2 ** s2 = calloc(BLOCK_SIZE, sizeof(void*));
    struct my_struct_3 ** s3 = calloc(BLOCK_SIZE, sizeof(void*));
    ...
}

However, if you are paranoid, you might want to take a look at these two discussions:

What is the difference between NULL, '\0' and 0

Are all data pointers the same size in one platform for all data types?

Community
  • 1
  • 1
igorrs
  • 350
  • 1
  • 7
  • I love this - simple, demonstrates how I want to be thinking about it, and how to progress from 'make it work' to 'make it work fast'. – Peter Jan 06 '12 at 02:15