0

This post is about understanding details in two pieces of C code. Why this is ok:

typedef struct _api_t api_t;

typedef void (*api_set) (api_t * api;
             int a;
  );

typedef int (*api_read) (api_t * api;
  );
  
struct _api_t
{
  api_set set;
  api_read read;
};

and this isn't

typedef struct _api_t
{
  api_set set;
  api_read read;
} 
api_t;

typedef void (*api_set) (api_t * api;
             int a;
  );

typedef int (*api_read) (api_t * api;
  );

error: unknown type name ‘api_set’, error: unknown type name ‘api_read’

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 5
    C is a single-pass parser. In the second one, you are using `api_set` and `api_read` before they are declared. – Christian Gibbons Jun 07 '22 at 18:45
  • The first code snippet is definitely not ok. – user3386109 Jun 07 '22 at 18:47
  • Moving typedef struct _api_t below typedef int (*api_read) doesn't help. Still the same problem. – Wojciech Mierzejewski Jun 07 '22 at 18:47
  • First piece of code does not spit any error. – Wojciech Mierzejewski Jun 07 '22 at 18:48
  • The first piece of code doesn't even come close to compiling. – user3386109 Jun 07 '22 at 18:49
  • @user3386109 What is wrong with it? – Cheatah Jun 07 '22 at 19:26
  • 1
    Note that you should not, in general, create function, variable, tag or macro names that start with an underscore. Part of [C11 §7.1.3 Reserved identifiers](https://port70.net/~nsz/c/c11/n1570.html#7.1.3) says: — _All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use._ — _All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces._ See also [What does double underscore (`__const`) mean in C?](https://stackoverflow.com/q/1449181) – Jonathan Leffler Jun 07 '22 at 19:46
  • @ChristianGibbons, Almost. C allows `goto` to a label that is not yet defined. – tstanisl Jun 07 '22 at 19:51

2 Answers2

1

These records

typedef void (*api_set) (api_t * api;
             int a;
  );

typedef int (*api_read) (api_t * api;
  );

are incorrect. The compiler should issue error messages.

It seems you mean the following code

typedef struct _api_t api_t;

typedef void (*api_set) (api_t * api, int a );

typedef int (*api_read) (api_t * api  );
  
struct _api_t
{
  api_set set;
  api_read read;
};

This code is correct because in this structure definition

struct _api_t
{
  api_set set;
  api_read read;
};

the names api_set and api_read (defined as function pointers in preceding typedefs) are already defined before they are used in the structure definition.

As for the structure definition in the second code snippet then the names api_set and api_read used in the structure definition are not yet defined

typedef struct _api_t
{
  api_set set;
  api_read read;
} 
api_t;

So the compiler will issue error messages that these names are not defined.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
0

About the semicolons as separator of function parameters:

typedef void (*api_set) (api_t * api; int a;);

This accidentally "works" because there is GCC extension allowing forward declarations of function parameters. If you run the compilation in the "pedantic" mode then you will see the warning:

warning: ISO C forbids forward parameter declarations [-Wpedantic]

The forward declarations of parameters is useful for functions taking Variable-Length Arrays (VLAa) as parameters because it allows to pass a parameter for array size after the parameter for an array.

  • without forward declaration
void foo(int n, int arr[static n]);

// calling example
int A[3];
foo(3, A);
  • with forward declaration
void foo(int n; int arr[static n], int n);

// calling example
int A[3];
foo(A, 3);

This feature was proposed to upcoming C23. See https://www9.open-std.org/JTC1/SC22/WG14/www/docs/n2780.pdf

In your code, the two parameters api and a are forward declared though never actually used. As result those forward declarations are ignored and the full declaration is equivalent to:

typedef void (*api_set) ();

Which a pointer to a function taking unspecified number of arguments and returning void. This function can be called with any number of arguments without raising a warning from a compiler. Moreover, this kind of type is incomplete as it is compatible with any function returning void independently from number and types of arguments used.

To fix the issue use , as a separator:

typedef void (*api_set) (api_t *api, int a);

Moreover, consider creating an alias for a function type rather than function pointer. It usually results in a clear code:

typedef struct _api_t api_t;

typedef void api_set_f (api_t *, int);
typedef int api_read_f (api_t *);
  
struct _api_t {
  // declare *pointer* to functions
  api_set_f* set;
  api_read_f* read;
};

In the second example the structure struct _api_t contains a member of type api_set, however this type is not defined at this stage of parsing.

tstanisl
  • 13,520
  • 2
  • 25
  • 40
  • AFAICS, the working draft for June 2022 (https://www9.open-std.org/JTC1/SC22/WG14/www/docs/N2912.pdf) does not incorporate the proposal from n2780. Do you know if it has still to be considered or whether it was rejected? My impression was that the next standard was "feature complete" and was now in 'fix up' mode for the next standard, but I may have missed something. – Jonathan Leffler Jun 07 '22 at 20:23
  • @JonathanLeffler, the minutes in n2991 says that there was no consensus about this feature – tstanisl Jun 07 '22 at 21:26
  • Thank you for the pointer. “No consensus” translates to “won't be in the standard”. Certainly not the current C2x standard (which will probably be C23 when it is finished). – Jonathan Leffler Jun 07 '22 at 21:51