Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Regarding typedefs of 1-element arrays in C

Tags:

arrays

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 an OO-sort-of-way, you might have alloc/free pairs like these:

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

Or, alternatively init/clear pairs (perhaps returning error-codes):

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

I have seen 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's create a ptr-type */

The alloc/free and init/clear pairs 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 it all like this (for instance, the 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. */
}

Remarks: The init/clear pair seems to allow for a more generic way of initializing and clearing out objects. Compared to the alloc/free pair, the init/clear pair requires that a "shallow" object has already been constructed. The "deep" construction is done using init.

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

like image 325
Ole Thomsen Buus Avatar asked Sep 01 '13 16:09

Ole Thomsen Buus


People also ask

Can we use typedef with Array?

In C programming language, typedef is also used with arrays.

How does typedef struct work in C?

The C language contains the typedef keyword to allow users to provide alternative names for the primitive (e.g.,​ int) and user-defined​ (e.g struct) data types. Remember, this keyword adds a new name for some existing data type but does not create a new type.

What is type define in C?

typedef is a reserved keyword in the programming languages C and C++. It is used to create an additional name (alias) for another data type, but does not create a new type, except in the obscure case of a qualified typedef of an array type where the typedef qualifiers are transferred to the array element type.


2 Answers

This is very clever (but see below).

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

If I see this in a C program:

foo bar;
foo_init(bar);

I know that the call to foo_init does not modify the value of bar. I also know that the code passes the value of bar to a function when it hasn't initialized it, which is very probably undefined behavior.

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

It is an attempt to make C look like something it's not, not unlike things like:

#define BEGIN {
#define END }

and so forth. And it doesn't result in code that's easier to understand because it uses features that C doesn't support directly. It results in code that's harder to understand (especially to readers who know C well), because you have to understand both the customized declarations and the underlying C semantics that make the whole thing work.

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

If you want to write code that looks like it's passing arguments by reference, define some function-like macros, and give them all-caps names so knowledgeable readers will know that something odd is going on.

I said above that this is "clever". I'm reminded of something I did when I was first learning the C language:

#define EVER ;;

which let me write an infinite loop as:

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

At the time, I thought it was clever.

I still think it's clever. I just no longer think that's a good thing.

like image 56
Keith Thompson Avatar answered Oct 19 '22 13:10

Keith Thompson


The only advantage to this method is nicer looking code and easier typing. It allows the user to create the struct on the stack without dynamic allocation like so:

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 every time.

foo_init(bar);

Without the 1 element array, it would require either an alloc function as you mentioned, or constant & usage.

foo_init(&bar);

The only pitfall I can think of is the normal concerns associated with direct stack allocation. If this in a library used by other code, updates to the struct may break client code in the future, which would not happen when using an alloc free pair.

like image 38
Justin Meiners Avatar answered Oct 19 '22 12:10

Justin Meiners