Regarding typedefs of 1-element arrays in C

Sometimes, in C, you do this:

typedef struct foo { unsigned int some_data; } foo; /* btw, foo_t is discouraged */ 

To use this new type in OO-sort-of-way, you may have free / loose pairs like these:

 foo *foo_alloc(/* various "constructor" params */); void foo_free(foo *bar); 

Or, alternatively, initialization / cleanup pairs (possibly returning error codes):

 int foo_init(foo *bar, /* and various "constructor" params */); int foo_clear(foo *bar); 

I saw the following idiom used, in particular in the MPFR library:

 struct foo { unsigned int some_data; }; typedef struct foo foo[1]; /* <- notice, 1-element array */ typedef struct foo *foo_ptr; /* let create a ptr-type */ 

Now alloc / free and init / clear pairs are now read:

 foo_ptr foo_alloc(/* various "constructor" params */); void foo_free(foo_ptr bar); int foo_init(foo_ptr bar, /* and various "constructor" params */); int foo_clear(foo_ptr bar); 

Now you can use all of this (e.g. init / clear pairs):

 int main() { foo bar; /* constructed but NOT initialized yet */ foo_init(bar); /* initialize bar object, alloc stuff on heap, etc. */ /* use bar */ foo_clear(bar); /* clear bar object, free stuff on heap, etc. */ } 

Notes . The initialization / cleanup pair seems to allow a more general way to initialize and cleanup objects. Compared to the alloc / free pair, the init / clear pair requires the small object to be constructed already. The deep construct is done using init.

Question : Are there any non-obvious traps for the 1-element array of "type-idiom"?

+7
c arrays
source share
2 answers

This is very smart (but see below).

He encourages the misleading idea that arguments to the C function can be passed by reference.

If I see this in a C program:

 foo bar; foo_init(bar); 

I know that calling foo_init does not change the value of bar . I also know that the code passes the bar value to the function if it did not initialize it, which is probably undefined behavior.

If I do not know that foo is a typedef for an array type. Then I suddenly realize that foo_init(bar) does not pass the value of bar , but the address of its first element. And now every time you see something that is of type foo or an object of type foo , I have to think about how foo was defined as typedef for a singleton array before I can understand the code.

This is an attempt to make C look like something similar, not like things like:

 #define BEGIN { #define END } 

etc. And this does not make the code easier to understand, since it uses functions that C does not directly support. This leads to a more complicated understanding of the code (especially for readers who know C well), because you need to understand both the customized declarations and the basic C semantics that make it all work.

If you want to pass pointers, just skip the pointers around and do it explicitly. See, for example, the use of FILE* in various standard functions defined in <stdio.h> . There is no attempt to hide pointers behind macros or typedefs, and C programmers have been using this interface for decades.

If you want to write code that looks like passing arguments by reference, define some macros, similar functions, and give them the names of all the caps so that knowledgeable readers know that something strange is happening.

I said above that it is "smart." I was reminded of something that I did when I first learned the C language:

 #define EVER ;; 

which allow me to write an infinite loop like:

 for (EVER) { /* ... */ } 

At that time I thought it was smart.

I still find it smart. I just don't think that is good anymore.

+7
source share

The only advantage of this method is more convenient viewing of the code and simplification of text input. It allows the user to create a structure on the stack without dynamic allocation as follows:

 foo bar; 

However, the structure can still be passed to functions that require a pointer type, without requiring the user to convert to a pointer with &bar each time.

 foo_init(bar); 

Without an array of 1 elements, it will require either the alloc function, as you mentioned, or the & constant.

 foo_init(&bar); 

The only error I can think of is the usual problems associated with direct stack allocation. If it is in the library used by other code, updates for the structure may break the client code in the future, which would not have happened when using a free pair.

+2
source share

All Articles