Advantage of using extern in a header file
Asked Answered
U

1

2

There is a similar question title here, but in reading the answers it doesn't seem to address that particular question: C: What is the use of 'extern' in header files? (it's more like a "Why use header files?").

In the following usages of extern:

extern int a;
int b;

// structs have no external linkage 
typedef struct Item_ {int id;} Item;
extern Item Item_Extern;

void main(void) {

}

// later in the program or another file
int a=4;
int b=5;
Item Item_Extern = {1};

Does the compiler produce any different assembly if extern is used, or is this more a metadata-tag for a type so that a user of the code (or compiler) will know explicitly that the definition will come in a different file (usually) or later on in the current file, and if it doesn't it'll be a hard error. Or, does extern 'do' anything else?

Upholsterer answered 4/2, 2021 at 23:2 Comment(16)
Yes, extern int a is only a declaration, but int b; is a tentative definition. You can look it up.Oro
The only usage of extern used in header files that makes sense is with the global variables defined in the corresponding C file, which should also be accessible in other C files.Peripheral
Does this answer your question? How do I use extern to share variables between source files?Mariannmarianna
@NateEldredge In his question he wrote "// later in the program or another file". By "the program" he obviously means this file with the shown code. In this case int b; is not a tentative definition.Morita
As I understand it, if int b; appears in this file (translation unit), and int b=5; appears in another file, then the former acts as a definition. So you have two definitions of the same object in different files, and thus the behavior is undefined. Am I mistaken?Oro
@VladfromMoscow: But I am pretty sure that int b; is a tentative definition whether int b = 5; appears later in the file or not. If it does, then int b; acts only as a declaration, but if it does not, then the tentative definition becomes a definition.Oro
@NateEldredge No, you are wrong. If there is the external definition int b = 5; in the translation unit then int b; in the same translation unit is not a tentative definition.Morita
@Mariannmarianna the best answer I can find (closest to the question) is probably this one, not on SO: quora.com/… or ibm.com/support/knowledgecenter/en/ssw_ibm_i_71/rzarg/….Upholsterer
@VladfromMoscow: Hmm, C17 6.9.2 (2): "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." The declaration int b; meets all those criteria. It does not become an actual definition when int b = 5; is elsewhere in the translation unit.Oro
@VladfromMoscow: But I was mostly following the case where int b = 5; is not in the same translation unit as int b; but in a different one, which OP also wanted to address ("... or another file"). Do we agree that this produces undefined behavior? And that if int b; were changed to extern int b; the behavior would be defined?Oro
@NateEldredge Oh, you are write. I thought that the tentative definition is when the compiler generates at the end of the scope its implicit definition.:)Morita
extern does what the standard says: introduces a declaration of an object that is defined elsewhere, so that code in the translation unit can access it without having to see the actual definition.Oneself
@NateEldredge You are substantially correct, but it's not UB. If three .c files have int b; the .o files have a C type symbol [common]. If they're linked with a fourth file that has int b = 5;, that .o puts it in .data (i.e. type D). Links fine and final executable has a D symbol in the .data section. If nobody does int b = 5;, the symbol in the final is .bss (type B). This linking mechanism is to support Fortran style common declarations. extern int b; is similar but the .o has a type U symbol. If nobody does int b; or int b = 5; it produces a link error.Erasion
@CraigEstey: My understanding is that it is UB under the C standard, and the "common block" behavior you describe is essentially an extension that certain implementations choose to provide. It's explained in https://mcmap.net/q/17012/-how-do-i-use-extern-to-share-variables-between-source-files under "not so good way", with Standard references.Oro
@CraigEstey: As Nate Eldredge says, the behavior is not defined by the C standard (per C 2018 6.9.2 2, a tentative definition without a definition in the same translation unit acts as if there were a declaration with a zero initializer, making it a definition, and, per 6.9 5, there shall be no more than one external definition for an identifier in a program). Unix tools have historically defined the behavior (the C standard does not compel its “undefined behavior” to remain undefined; other things may step in) to resolve as you say, and GCC did too, but GCC changed in somewhat recent versions.Episodic
@EricPostpischil want to post an answer to clarify things?Upholsterer
E
2

Introduction

There are three forms of declaration of concern here:1

extern int x; // Declares x but does not define it.
int x;        // Tentative definition of x.
int x = 0;    // Defines x.

A declaration makes an identifier (a name, like x) known.

A definition creates an object (such as an int).2 A definition is also a declaration, since it makes the name known.

A tentative definition without a regular definition in the same translation unit (the source file being compiled, with all of its included files) acts like a definition with an initializer of zero.

The way you should use these normally is:

  • For an object you will access by name in multiple files, write exactly one definition of it in one source file. (It can be a tentative definition3 if you would like it to be initialized with zero, or it can be a regular definition with an initializer you choose.)
  • In an associated header file (such as foo.h for the source file foo.c), declare the name, using extern as shown above.
  • Include the header file in each file that uses the name, including its associated source file. (The latter is important; when foo.c includes foo.h, the compiler will see both the declaration and the definition in the same compilation and give you a warning if there is a typo that makes the two declarations incompatible.)

Actually, the way you should use these normally is not to use them at all. Programs generally do not need external identifiers for objects, so you should design the program without them. The rules above are for when you do use them.

Tentative Definitions

In Unix and some other systems, it has been possible to put a tentative definition, int x;, in a header file and include it in multiple source files. Because a tentative definition acts like a definition in the absence of a regular definition, this results in there being multiple definitions in multiple translation units. The C standard does not define the behavior of this. So how does it work in Unix?

Until recently, when you compiled with GCC (as built by default), it created an object file that marked tentatively defined identifiers differently from regularly defined identifiers. The tentatively defined identifiers were marked as “common.” When the linker found multiple definitions of a “common” identifier, it coalesced them into a single definition. Remember, the C standard does not define the behavior. But Unix tools4 did. So you could put int x; in a header and include it in lots of places, and you would get one int x out of it when linking the entire program.

In version 10 and later, GCC does not do this by default. Tentative definitions are, in the absence of regular definitions, treated more like regular definitions, and linking with multiple definitions of the same identifier will result in an error, even if the definitions arose from tentative definitions. GCC has a switch to select the old behavior, -fcommon.

This is information you should know so that you can understand old source files and headers that took advantage of the “common” behavior. It is not needed in new source code, and you should write only non-definition declarations (using extern) in headers and regular definitions in source files.

Miscellaneous

You do not need extern with a function declaration because a function declaration without a body (the compound statement that contains the code for the function) is automatically a declaration and behaves the same as if it had extern. Functions do not have tentative definitions.

Footnote

1 This answer addresses only external declarations and external definitions for identifiers of objects, with external linkage. The full rules for C declarations are somewhat complicated, partly due to the history of C’s evolution.

2 This is for definitions of identifiers that refer to objects. For other kinds of identifiers, what is a definition may be different. For example, typedef int foo is said to define foo as an alias for the type int, but no object is created.

3 It may be preferable to also include an initializer, even if it is zero, as this will make it a regular definition and avoid a potential problem where the same name is used tentative definitions in two different source files for two different things, resulting in the linker not complaining even though this is an error.

4 I may be being sloppy with terminology here; somebody else could identify precisely where this behavior was specified and what tools it applied to.

Episodic answered 5/2, 2021 at 0:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.