Defining variable in C header file related doubt

W

whirlwindkevin

I saw a program source code in which a variable is defined in a header
file and that header file is included in 2 different C files.When i
compile and link the files no error is being thrown.How is this
possible.I thought it will throw "Variable redefinition Error". Giving
the source code for reference.Please help me out on this...

a.h
-----
int g;
void fun(void);

a.c
-----
#include "a.h"
void main()
{
g = 50;
printf("%d\n",g);
fun();
printf("%d\n",g);
}

b.c
----
#include "a.h"
void fun(void)
{
g = 120;
}

Output is 50...120

Regards
Manu
 
W

whirlwindkevin

whirlwindkevin said:


And it didn't compile, obviously.


Then you didn't see what you thought you saw.



This is not *exactly* a definition. It's a "tentative definition", and it's
one of the silliest ideas in C - a silly feature of the language, and
silly of the programmer to exploit the feature.

Some terms:
A *declaration* is you telling the compiler "see this identifier? When I
use it, I'm talking about an object (or function) of such-and-such a type,
and just trust me that this object exists, okay?" On that understanding, a
compiler can go ahead and compile code that can interact even with objects
or functions that it can't see right now (much as a carpenter can make a
letterbox in a door even without seeing the actual letters that will drop
through it, provided you tell him how big the letters will be). But if you
lie to the compiler, you'll pay later.

A *definition* is you telling the compiler "make me an object of
such-and-such a type" (or a "function that does THIS"). If a declaration
is a promise, then a definition is a way of honouring that promise. A
definition *is* a declaration (because, when you create an object, that's
as good a way as any of informing the compiler that the object exists),
but declarations need not be definitions.

A "tentative definition" is you telling the compiler "it's my intent to
create an object of this type, but I'm not really sure whether this is the
right place to do it". The Standard says:

"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. If a translation unit contains one or more tentative
definitions for an identifier, and the translation unit contains no
external definition for that identifier, then the behavior is exactly
as if the translation unit contains a file scope declaration of that
identifier, with the composite type as of the end of the translation
unit, with an initializer equal to 0."

Best advice? Avoid like the plague. Instead, if you must use file scope
object identifiers, do it this way:

1) in exactly one header that can be included by all relevant sources,
write a declaration, such as:

extern int IfYouMustDoThisDamnSillyThing;

2) in the same header, define a sensible initial value for the object:

#define DO_IT_LIKE_THIS 42

3) include that header in all relevant sources;

4) in exactly one source, write a definition at file scope, such as:

int IfYouMustDoThisDamnSillyThing = DO_IT_LIKE_THIS;


The main function returns int. If you return anything else from main,
you're breaking the rules of C, and C is released from any obligation to
be predictable.

--
Richard Heathfield <http://www.cpax.org.uk>
Email: -http://www. +rjh@
Google users: <http://www.cpax.org.uk/prg/writings/googly.php>
"Usenet is a strange place" - dmr 29 July 1999

Many Thanks Richard for replying...
Still i have a doubt related to definition of the variable.Can you
please tell where variable g (variable in the above program) is
defined (space is allocated for the variable)? Is it in a.c or is it
in b.c?
 
C

Chris Torek

[regarding two separate .c files, both with "int g;" with no
initializer for either one, that are compiled separately and
then linked]

Well, actually, it did (for him). But as you note:

Tentative definitions are actually useful in a few corner cases,
such as creating a circularly linked list at compile time that
has several elements:

struct foolist {
struct foolist *next, *prev;
... more data here ...
};

static struct foolist foo_head, foo_elem_2, foo_tail;

static struct foo_list foo_head = { &foo_elem_2, &foo_tail, ... };
static struct foo_list foo_elem_2 = { &foo_tail, &foo_head, ... };
static struct foo_list foo_tail = { &foo_head, &foo_elem_2, ... };

In C, you must use tentative definitions to make this work. (For
non-"static" variables, which have external linkage, you can use
"extern" to avoid creating tentative definitions.)

However, as one can see if one has an implementation like the OP's,
this is not quite true:

% cat a.define.c
int x = 0;
% cat b.define.c
int x = 0;
int main(void) { return 0; }
% cc -o define a.define.c b.define.c
/tmp/ccodx0za.o(.data+0x0): multiple definition of `x'
/tmp/ccV8U2Fi.o(.data+0x0): first defined here
% cat a.tent.c
int x;
% cat b.tent.c
int x;
int main(void) { return 0; }
% cc -o tent a.tent.c b.tent.c
%

So, we now have to ask: is this implementation broken? The behavior
differs for tentative definitions and actual (non-tentative)
definitions, despite the above quote from the C standard.

The answer is no, because what the Standard giveth at this point,
the Standard taketh away elsewhere:

K.2 Undefined behavior

[#1] The behavior is undefined in the following circumstances:
[massive snippage]
- An identifier with external linkage is used, but in the
program there does not exist exactly one external definition
for the identifier, or the identifier is not used and there
exist multiple external definitions for the identifier.
(6.7).

In other words, the implementation can do anything it wants if you
define the same identifier twice (or any other number of times than
1 or 0, with "defined 0 times" being OK only for one particular
case).

The OP's implementation, like mine above, uses this "undefined
behavior" license to provide a "feature" of sorts: tentative
definitions are turned into actual definitions, but not at the end
of each translation unit. Instead, the compiler waits until the
last possible moment -- the "link" phase of compilation -- and only
*then* turns all remaining tentative definitions into actual
definitions. In the process, it saves a bit of disk space. This
feature also lets lazy programmers leave out the keyword "extern".
Many Thanks Richard for replying...
Still i have a doubt related to definition of the variable.Can you
please tell where variable g (variable in the above program) is
defined (space is allocated for the variable)? Is it in a.c or is it
in b.c?

According to the Standard, it is in both. However, your implementation
(like mine) makes use of the undefined behavior to cause it to
happen in *neither*. It happens in a third, "invisible" source
file that your compiler makes up at the last possible moment. (In
my implementation, this is not even an actual file at all, although
some compilers do use an extra file, and even a program called
"collect2", to handle some tricky cases, particularly when mixing
C with other languages. If your linker is smart enough, it can
create a sort of "pretend" file purely in RAM, and avoid the
"collect2" step. This job is a lot easier for pure C programs; it
is only those other langauges that create the need for "collect2".)
 
B

Bartc

Richard Heathfield said:
whirlwindkevin said:


And it didn't compile, obviously.


Then you didn't see what you thought you saw.


This is not *exactly* a definition. It's a "tentative definition"

Looks like an ordinary definition to me. What's tentative about it?

The OP's question was why, with int g declared in both files (and apparently
sharing the same space) no error was raised.
 
R

Richard

Bartc said:
Looks like an ordinary definition to me. What's tentative about it?

You need to look up the meaning of tentative definition. But you are
really (in the real world) quite correct. In normal C language it is a
perfectly normal definition.

However in language lawyreville:

,----
| A tentative definition is any external data declaration that has no
| storage class specifier and no initializer. A tentative definition
| becomes a full definition if the end of the translation unit is reached
| and no definition has appeared with an initializer for the
| identifier. In this situation, the compiler reserves uninitialized space
| for the object defined.
`----

http://publib.boulder.ibm.com/infoc...p6m.doc/language/ref/clrc03tentative_defn.htm

or

http://tinyurl.com/2fqgx7

In many years of professional programming the issue/word has never come
up for me.
 
R

Richard Tobin

[/QUOTE]
;>>>> int g;
You need to look up the meaning of tentative definition.

Or he could just have read it, since it was quoted in the article he
replied to.

-- Richard
 
L

lawrence.jones

whirlwindkevin said:
I saw a program source code in which a variable is defined in a header
file and that header file is included in 2 different C files.When i
compile and link the files no error is being thrown.How is this
possible.I thought it will throw "Variable redefinition Error". Giving
the source code for reference.Please help me out on this...

Having multiple definitions results in undefined behavior -- the
implementation is not required to diagnose it, and many (if not most)
implementations will not, particularly if the definitions are compatible
and the variable is not explicitly initialized. The code is still
wrong: the header should have only a declaration of the variable (with
extern) and exactly one source file should have the actual definition.

-- Larry Jones

OK, what's the NEXT amendment say? I know it's in here someplace. -- Calvin
 
J

James Kuyper

Bartc said:
Looks like an ordinary definition to me. What's tentative about it?

Other people have told you that it's tentative because the standard
defines it to be tentative, and have cited the relevant clause. However,
they didn't say why the standard chose the word "tentative" for this
purpose.

The reason it is tentative is that this definition of 'g' has
characteristics that can be changed by a following definition in the
same translation unit. If there are no other definitions, then 'g' will
have external linkage and be zero-initialized. However, if a following
declaration were to say:

static int g;

Then 'g' would still be tentative, but would have internal linkage.
Alternatively, if a following declaration were to say:

extern int g;

then 'g' would no longer be tentative; it would definitely have external
linkage. Note: if different declarations specify both internal and
external linkage, then the behavior is undefined. Finally, if as
tentative declaration of 'g' were followed by

int g = 10;

the 'g' would not be zero-initialized; it would be initialized with a
value of 10.
 
M

manu

Other people have told you that it's tentative because the standard
defines it to be tentative, and have cited the relevant clause. However,
they didn't say why the standard chose the word "tentative" for this
purpose.

The reason it is tentative is that this definition of 'g' has
characteristics that can be changed by a following definition in the
same translation unit. If there are no other definitions, then 'g' will
have external linkage and be zero-initialized. However, if a following
declaration were to say:

static int g;

Then 'g' would still be tentative, but would have internal linkage.
Alternatively, if a following declaration were to say:

extern int g;

then 'g' would no longer be tentative; it would definitely have external
linkage. Note: if different declarations specify both internal and
external linkage, then the behavior is undefined. Finally, if as
tentative declaration of 'g' were followed by

int g = 10;

the 'g' would not be zero-initialized; it would be initialized with a
value of 10.

Thanks Guys for the help....
 

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,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top