Forward declaration of static variable

J

Jef Driesen

I have the following problem in a C project (but that also needs to
compile with a C++ compiler). I'm using a virtual function table, that
looks like this in the header file:

typedef struct device_t {
const device_backend_t *backend;
...
} device_t;

typedef struct device_backend_t {
int (*read) (device_t *device, /* parameters */);
int (*write) (device_t *device, /* parameters */);
...
} device_backend_t;

Now when I want to implement a new backend, I write the necessary
functions in the source file:

int mydevice_read (device_t *device, /* parameters */)
{
...
}

int mydevice_write (device_t *device, /* parameters */)
{
...
}

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

So far no problem, but in a number of those functions, I need to have
access to the "mydevice_backend" variable. For instance to check whether
the backend pointer is the correct one. How can I forward declare this
variable properly?

When I add

static const device_backend_t reefnet_sensuspro_device_backend;

to the top of my source file, it works with the gcc compiler, but I'm
not sure this is valid according to the C (or C++) standard. It
certainly doesn't compile with msvc (in C++ mode). It complains about a
redefinition.

How can I make this work? I could move the definition of the
"mydevice_backend" variable to the top of the source file and forward
declare each function inside the structure, but being able to forward
declare the variable itself would be much more elegant.
 
B

Ben Bacarisse

Jef Driesen said:
I have the following problem in a C project (but that also needs to
compile with a C++ compiler). I'm using a virtual function table, that
looks like this in the header file:

typedef struct device_t {
const device_backend_t *backend;
...
} device_t;

typedef struct device_backend_t {
int (*read) (device_t *device, /* parameters */);
int (*write) (device_t *device, /* parameters */);
...
} device_backend_t;

Now when I want to implement a new backend, I write the necessary
functions in the source file:

int mydevice_read (device_t *device, /* parameters */)
{
...
}

int mydevice_write (device_t *device, /* parameters */)
{
...
}

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

So far no problem, but in a number of those functions, I need to have
access to the "mydevice_backend" variable. For instance to check
whether the backend pointer is the correct one. How can I forward
declare this variable properly?

When I add

static const device_backend_t reefnet_sensuspro_device_backend;

I think you mean

static const device_backend_t mydevice_backend;

At least that is the name you use above and below.
to the top of my source file, it works with the gcc compiler, but I'm
not sure this is valid according to the C (or C++) standard. It
certainly doesn't compile with msvc (in C++ mode). It complains about
a redefinition.

You may get a better answer in comp.lang.c++ since this whole area is
one in which there are subtle but important differences between C and
C++. If you need the code to pass through a C++ compiler, then you
are writing C++ (no matter how much it looks like C!) and you need C++
experts.

I you had said it failed on MSVC in C mode I would have written: It is
likely that exact details are required so please construct a minimal
example that compiles in one but not the other compiler (you are 99%
there already with what you outlined).

The reason I say this is that "at the top" is not clear enough for
this problem because the declaration of the static struct is what is
called (in C) a tentative definition and special rules apply. Since
it has internal linkage, it must be a complete type (by the standard)
but gcc accepts it even when incomplete (it is allowed to -- there is
no requirement for a diagnostic message).

[In fact I wrote that before I saw your "in C++ mode" remark. I
suspect you don't need C's answer to this question.]
How can I make this work? I could move the definition of the
"mydevice_backend" variable to the top of the source file and forward
declare each function inside the structure, but being able to forward
declare the variable itself would be much more elegant.

You can in C, but maybe not in C++. The C++ people can tell you.
 
Z

zaimoni

I have the following problem in a C project (but that also needs to
compile with a C++ compiler). I'm using a virtual function table, that
looks like this in the header file:

typedef struct device_t {
const device_backend_t *backend;
...

} device_t;

typedef struct device_backend_t {
int (*read) (device_t *device, /* parameters */);
int (*write) (device_t *device, /* parameters */);
...

} device_backend_t;
.....

So far no problem, but in a number of those functions, I need to have
access to the "mydevice_backend" variable. For instance to check whether
the backend pointer is the correct one. How can I forward declare this
variable properly?

When I add

static const device_backend_t reefnet_sensuspro_device_backend;

to the top of my source file, it works with the gcc compiler, but I'm
not sure this is valid according to the C (or C++) standard. It
certainly doesn't compile with msvc (in C++ mode). It complains about a
redefinition.

Just to be clear, does g++ accept that, or just gcc?

In C(99), that is a tentative definition; it can use a later
initializer to do the actual initialization. [C99 standard: 6.9.2
paragraph 2] Tentative definitions were intentionally removed from C+
+98 [Informative Annex C.2.2 paragraph 1]; this indeed is a
redefinition in C++.

From what you describe, I don't think extern would be a good idea
(that should work for forward-declaring a file-scope variable in both
C and C++).
How can I make this work? I could move the definition of the
"mydevice_backend" variable to the top of the source file and forward
declare each function inside the structure, but being able to forward
declare the variable itself would be much more elegant.

Assuming static is the correct linkage, I'd just forward-declare the
functions to be portable.
 
J

Jef Driesen

Ben said:
I think you mean

static const device_backend_t mydevice_backend;

At least that is the name you use above and below.

You're correct. This was a copy-and-paste error.
You may get a better answer in comp.lang.c++ since this whole area is
one in which there are subtle but important differences between C and
C++. If you need the code to pass through a C++ compiler, then you
are writing C++ (no matter how much it looks like C!) and you need C++
experts.

I you had said it failed on MSVC in C mode I would have written: It is
likely that exact details are required so please construct a minimal
example that compiles in one but not the other compiler (you are 99%
there already with what you outlined).

The reason I say this is that "at the top" is not clear enough for
this problem because the declaration of the static struct is what is
called (in C) a tentative definition and special rules apply. Since
it has internal linkage, it must be a complete type (by the standard)
but gcc accepts it even when incomplete (it is allowed to -- there is
no requirement for a diagnostic message).

With "at the top" I meant after the definition of device_t and
device_backend_t types, but right before the mydevice_read,
mydevice_write functions.
[In fact I wrote that before I saw your "in C++ mode" remark. I
suspect you don't need C's answer to this question.]

The C++ mode is only used for compiling in msvc, because I'm using a
number of C99 features (variable declarations anywere in a code block),
that are not supported in the msvc C compiler. In linux, I am compiling
in C99 mode with gcc.
 
J

Jef Driesen

Just to be clear, does g++ accept that, or just gcc?

gcc in C99 mode. I didn't try with g++ yet.
In C(99), that is a tentative definition; it can use a later
initializer to do the actual initialization. [C99 standard: 6.9.2
paragraph 2] Tentative definitions were intentionally removed from C+
+98 [Informative Annex C.2.2 paragraph 1]; this indeed is a
redefinition in C++.

From what you describe, I don't think extern would be a good idea
(that should work for forward-declaring a file-scope variable in both
C and C++).

How would I use extern here?
Assuming static is the correct linkage, I'd just forward-declare the
functions to be portable.

Static is correct. The mydevice_backend variable only needs to be
"visible" at file-scope.
 
B

Barry Schwarz

I have the following problem in a C project (but that also needs to
compile with a C++ compiler). I'm using a virtual function table, that

Do you mean you are writing a C++ program this will be used by a
project that is mostly in C or do you need a program that compiles
correctly in both? I will assume the latter. In spite of the
similarity in the language names and a good deal of common syntax, the
two are separate languages. Trying to write code that will compile in
both will force you to limit yourself to the common subset and will
also require you to make choices which are undesirable in at least one
of the languages.
looks like this in the header file:

typedef struct device_t {
const device_backend_t *backend;

In C, the structure tag is not a type. Before this typedef, add the
"incomplete" typedef
typedef struct device_backend_t device_backend_t;
so that the type device_backend_t is known and
...
} device_t;

typedef struct device_backend_t {

then change this typedef to a simple structure declaration.
int (*read) (device_t *device, /* parameters */);
int (*write) (device_t *device, /* parameters */);
...
} device_backend_t;

Now when I want to implement a new backend, I write the necessary
functions in the source file:

int mydevice_read (device_t *device, /* parameters */)
{
...
}

int mydevice_write (device_t *device, /* parameters */)
{
...
}

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

So far no problem, but in a number of those functions, I need to have
access to the "mydevice_backend" variable. For instance to check whether
the backend pointer is the correct one. How can I forward declare this
variable properly?

When I add

static const device_backend_t reefnet_sensuspro_device_backend;

to the top of my source file, it works with the gcc compiler, but I'm
not sure this is valid according to the C (or C++) standard. It

Defining a static variable a file scope is valid. As with any
"global" variable, it is visible to every function within the source
file and exists for the life of the program. Since it is static, it
has internal linkage which means the name of the variable will not be
exported to the linker. If another source file uses the same variable
name, it will represent a different variable. Since you don't provide
initialization for this object with static duration, it will
automatically be initialized to the appropriate form of zero. As the
structure contains two pointers, they will both be set to NULL. But
because you declare the structure as const, you will not be able to
change the value of either pointer.
certainly doesn't compile with msvc (in C++ mode). It complains about a
redefinition.

Since the only thing being defined is the object named
reefnet...backend, you must have another definition of that object in
code that precedes this definition. It could be in a header (which
should never have definitions anyway).
How can I make this work? I could move the definition of the

What move? Your previous paragraph said it was already at the top.
Where is it really?
"mydevice_backend" variable to the top of the source file and forward
declare each function inside the structure, but being able to forward
declare the variable itself would be much more elegant.

If you put your struct declarations/typedefs and prototypes for each
function in a header and include the header before any of your
definitions, you can define this object at the top of your code and
initialize it to point to the functions you want.

file.h:
typedef struct device_backend_t device_backend_t;

typedef struct device_t {
const device_backend_t *backend;
...
} device_t;

struct device_backend_t {
int (*read) (device_t *device, /* parameters */);
int (*write) (device_t *device, /* parameters */);
...
};

int mydevice_read (device_t *device, /* parameters */);

int mydevice_write (device_t *device, /* parameters */);

--------------------------------------------------------------

file.c:
#include "file.h"

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

static const device_backend_t reefnet_sensuspro_device_backend =
{...};

main and mydevice_read etc to follow.
 
B

Ben Bacarisse

Jef Driesen said:
Ben Bacarisse wrote:

With "at the top" I meant after the definition of device_t and
device_backend_t types, but right before the mydevice_read,
mydevice_write functions.

OK, then at that point the type is not incomplete and a C compiler
should take that as a tentative defintions -- one that gets "rounded
out" by the later details. I understand that this is true in C90 as
well as in C99 (I am less sure of some details of C90). I.e. if this
were all you were doing, you would not need to push MSVC into C++ mode
since it complies (with the right switches) to C90.
[In fact I wrote that before I saw your "in C++ mode" remark. I
suspect you don't need C's answer to this question.]

The C++ mode is only used for compiling in msvc, because I'm using a
number of C99 features (variable declarations anywere in a code
block), that are not supported in the msvc C compiler. In linux, I am
compiling in C99 mode with gcc.

I see. Other parts of the code require more than MSVC C offers. It
seems daft that MSVC does not have a "near" C99 mode, but that is the
way the world is. As soon as you take C++ sematics, const changes and
tentative definitions are lost. Is another compiler an option for you
Windows build?

If you do ask in comp.lang.c++ I suspect the answers will involve
using more C++ features since, having just had a peek, I see the C++
standard is very clear that tentative definitions are out.

[Ah! I see this is now cross-posted so my assertions about C++ will
be tested -- just ignore the phrases "if you ask in...".]
 
Z

zaimoni

Just to be clear, does g++ accept that, or just gcc?

gcc in C99 mode. I didn't try with g++ yet.
In C(99), that is a tentative definition; it can use a later
initializer to do the actual initialization. [C99 standard: 6.9.2
paragraph 2] Tentative definitions were intentionally removed from C+
+98 [Informative Annex C.2.2 paragraph 1]; this indeed is a
redefinition in C++.
From what you describe, I don't think extern would be a good idea
(that should work for forward-declaring a file-scope variable in both
C and C++).

How would I use extern here?

Since the full definition is later on in the same file, use

extern const device_backend_t mydevice_backend;

to get the forward declaration in both C and C++. Unfortunately,
locking down an extern declaration to file scope is feasible only in C+
+ (wrap both the forward declaration, and the actual definition, in an
anonymous namespace).

If your coding standards tolerate the C preprocessor, something like
this (subject to actual compile testing not done here) would work by
giving each language what it wants:

#ifdef __cplusplus
namespace {
extern const device_backend_t mydevice_backend;
}
#else
static const device_backend_t mydevice_backend;
#endif

(also using the preprocessor to wrap the actual definition in an
anonymous namespace for C++.)
 
J

Jef Driesen

Barry said:
Do you mean you are writing a C++ program this will be used by a
project that is mostly in C or do you need a program that compiles
correctly in both? I will assume the latter. In spite of the
similarity in the language names and a good deal of common syntax, the
two are separate languages. Trying to write code that will compile in
both will force you to limit yourself to the common subset and will
also require you to make choices which are undesirable in at least one
of the languages.

It's a C library. The reason why I would like to be able to compile it
with a C++ compiler is that I'm using a number of C99 features (variable
declarations anywhere in a code block). Unfortunately the msvc C
compiler does not support C99, but those features are supported in C++,
so I'm trying to make it build in C++ mode.
In C, the structure tag is not a type. Before this typedef, add the
"incomplete" typedef
typedef struct device_backend_t device_backend_t;
so that the type device_backend_t is known and


then change this typedef to a simple structure declaration.

I'm doing it like that in my source code. I omitted the forward
declarations in this post to make it shorter.
Defining a static variable a file scope is valid. As with any
"global" variable, it is visible to every function within the source
file and exists for the life of the program. Since it is static, it
has internal linkage which means the name of the variable will not be
exported to the linker. If another source file uses the same variable
name, it will represent a different variable. Since you don't provide
initialization for this object with static duration, it will
automatically be initialized to the appropriate form of zero. As the
structure contains two pointers, they will both be set to NULL. But
because you declare the structure as const, you will not be able to
change the value of either pointer.


Since the only thing being defined is the object named
reefnet...backend, you must have another definition of that object in
code that precedes this definition. It could be in a header (which
should never have definitions anyway).

Sorry, that was a copy-and-paste error. The variable was supposed to be
named "mydevice_backend".
What move? Your previous paragraph said it was already at the top.
Where is it really?

I only have a declaration (without any initialization) at the top. The
initialization is done after the implementation of the mydevice_*
functions. So the file looks like this:

static const device_backend_t mydevice_backend;

int mydevice_read (device_t *device, /* parameters */)
{
...
}

int mydevice_write (device_t *device, /* parameters */)
{
...
}

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

With "moving to the top", I meant forward declaring the functions at the
top of the file, so the initialization can be done at the top as well.
Now will look like this:

int mydevice_read (device_t *device, /* parameters */);
int mydevice_write (device_t *device, /* parameters */);

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

int mydevice_read (device_t *device, /* parameters */)
{
...
}

int mydevice_write (device_t *device, /* parameters */)
{
...
}

But in that case I have to forward declare every single function that is
listed in the virtual function table, while all I need would be a
forward declaration of the "mydevice_backend" variable.
If you put your struct declarations/typedefs and prototypes for each
function in a header and include the header before any of your
definitions, you can define this object at the top of your code and
initialize it to point to the functions you want.

file.h:
typedef struct device_backend_t device_backend_t;

typedef struct device_t {
const device_backend_t *backend;
...
} device_t;

struct device_backend_t {
int (*read) (device_t *device, /* parameters */);
int (*write) (device_t *device, /* parameters */);
...
};

int mydevice_read (device_t *device, /* parameters */);

int mydevice_write (device_t *device, /* parameters */);

--------------------------------------------------------------

file.c:
#include "file.h"

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

static const device_backend_t reefnet_sensuspro_device_backend =
{...};

main and mydevice_read etc to follow.

That is essentially the same as what I wrote above, except that I'm not
using a header file. Because this is all "private" and used only inside
the c file. My header file contains only the "public" part of the api.
 
I

Ian Collins

Jef said:
It's a C library. The reason why I would like to be able to compile it
with a C++ compiler is that I'm using a number of C99 features (variable
declarations anywhere in a code block). Unfortunately the msvc C
compiler does not support C99, but those features are supported in C++,
so I'm trying to make it build in C++ mode.
So it's now a C++ library, so you may as well update the code accordingly.
 
B

Barry Schwarz

snip

snip

I only have a declaration (without any initialization) at the top. The
initialization is done after the implementation of the mydevice_*
functions. So the file looks like this:

static const device_backend_t mydevice_backend;

This is a definition because static object have an implied
initialization if one is not provided.
int mydevice_read (device_t *device, /* parameters */)
{
...
}

int mydevice_write (device_t *device, /* parameters */)
{
...
}

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

And then this very obviously is a duplicate definition.
With "moving to the top", I meant forward declaring the functions at the
top of the file, so the initialization can be done at the top as well.
Now will look like this:

int mydevice_read (device_t *device, /* parameters */);
int mydevice_write (device_t *device, /* parameters */);

static const device_backend_t mydevice_backend = {
mydevice_read,
mydevice_write,
...
};

int mydevice_read (device_t *device, /* parameters */)
{
...
}

int mydevice_write (device_t *device, /* parameters */)
{
...
}

But in that case I have to forward declare every single function that is
listed in the virtual function table, while all I need would be a

Yes. But it is a simple cut and paste since you can use the same
first line(s) from the definition for your prototype. Just add a
semicolon.
forward declaration of the "mydevice_backend" variable.

There is something called a tentative definition. I don't know if the
rules in C and C++ are sufficiently similar for it to be of value. I
also don't know if you can have a tentative definition for an object
with static duration.
 
J

jameskuyper

Barry said:
This is a definition because static object have an implied
initialization if one is not provided.

In C, because this is only a tentative definition, the initialization
is implicit only if there is no later non-tentative definition of the
same object with an explicit definition.
And then this very obviously is a duplicate definition.


Yes. But it is a simple cut and paste since you can use the same
first line(s) from the definition for your prototype. Just add a
semicolon.


There is something called a tentative definition. I don't know if the
rules in C and C++ are sufficiently similar for it to be of value. I
also don't know if you can have a tentative definition for an object
with static duration.

"A declaration of an identifier for an object that has file scope
without an initializer, and without a storage-class specifier or with
the storage-class specifier static, constitutes a tentative
definition." (6.9.2p2)

As far as I can see, 'mydevice_backend" does have file scope, does not
have an (explicit) initializer, and the only strorage-class specifier
it has is "static", which is explicitly permitted. Therefore, it does
qualify as a tentative definition in C. As a result, it is perfectly
legal to follow it up with other tentative definitions, or with a non-
tentative definition. The different definitions must be of compatible,
as described in 6.2.7. Since device_backend_t is typedef for a struct
type, about the only meaningful freedom allowed for the second
declaration to differ from the first is by providing an initializer.

The problem is that C++ has no such concept, so code which must
compile both as C code and as C++ code can't make use of that fact.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,968
Messages
2,570,150
Members
46,697
Latest member
AugustNabo

Latest Threads

Top