Why can I define a variable twice in C?
Asked Answered
S

1

4

I've been testing global variables, defining and declaring, and I stopped at this situation:

main.c:

#include "stdio.h"

void func(void);
int a;

int main(void) {
    a = 20;
    printf("in main: %d\n", a);
    func();
    
    return 0;
}

add.c:

#include <stdio.h>

void func(void);
int a;

void func() {
    printf("in add: %d\n", a);
}

so in C the line

int a;

means both declaration and definition, but we know that defining a variable more than once is not allowed. So why does this code compile if we have two definitions and two declarations of a? I'm working in CLion and when I press "Go to Definition/Declaration" on a in main, it moves the pointer to a in add.c, when I do the same in add.c, it moves back to main.c, so I can't understand what's happening in here.

Sheepherder answered 26/4, 2021 at 16:9 Comment(4)
What you might want here is a add.h file that has void func(void); and extern int a;. Then have a #include "add.h" in your main.c. Global variables are nasty, however.Irenics
When you do (e.g.) int a; it is a "common" symbol. See my answer: #64627417 for an explanationPosh
Compile both main.c and add.c with the GCC compiler invoked as gcc -Wall -Wextra -g -c then link with gcc main.o add.o -o yourprog then use the GDB debugger on yourprog executable. Do read documentation of both your compiler and your debuggerPourboire
That the behavior of your program is undefined as a result of there being two external definitions of identifier a does not mean you can rely on the compiler to reject the program or even diagnose the issue. "Undefined" means "undefined".Trapshooting
S
14

Outside of any function, int x; is a tentative definition, and some compilers and linkers treat them as a sort of “cooperative definition,” where an identifier can be declared this way in multiple files and will result in defining only one object.

C’s rules for external declarations (declarations outside of functions) are a bit complicated due to history—C grew with different people developing and experimenting, rather than by design with the knowledge we have today.

Definitions: int x = 3; is a definition. It both declares the identifier x and reserves memory for an int, and it initializes the int to 3.

Declarations: extern int x; is a declaration but not a definition. It declares the identifier x but does not reserve memory for it.

extern int x; gives x external linkage, and so does int x = 3; if it appears outside of a function. External linkage means, when they appear in different source files, the two instances of the identifier will be linked to refer to the same thing in memory.

The C standard says “there shall be” at most one definition for an identifier with external linkage (C 2018 6.9 5). (If the identifier is used in the program, there must be a definition. If it is not used in an expression, you do not need a definition.)

Tentative definitions: int x; is a hybrid. Outside of a function, it is a special kind of declaration called a tentative definition. The C standard says that, if there is a tentative definition in a translation unit (the source file being compiled, along with all the files it includes) and there is no regular definition, a regular definition will be created.

Now, what happens if you violate the rule that “there shall be” at most one definition? Here is the thing: It is not a rule a program has to obey. When the C standard says “shall” it means, if a program obeys this rule, the behavior will be as the C standard says. If a program disobeys this rule, the C standard does not define the behavior (C 2018 4 2). Instead, we let the compiler and the linker define the behavior, if their designers elect to do so.

A common behavior (but not the only possibility) in compilers and linkers when a program violates the rule about at most one definition was:

  • If there are multiple regular definitions when linking, report an error.
  • If there are multiple definitions coming from tentative definitions but no more than one coming from a regular definition, coalesce them into a single definition.

This was the defined default behavior in GCC and associated tools prior to GCC version 10 and was explicitly mentioned in the C 2018 standard’s informative section on common extensions, in J.5.11. In current versions of GCC, multiple definitions of any type are treated as an error by default. You can request the old behavior with the command-line switch -fcommon.

Samarium answered 26/4, 2021 at 16:30 Comment(2)
Worth mentioning Annex J.5.11 (Common extensions - Multiple external definitions) which many compilers implementHaney
So from a C standard perspective, the short answer is that it is undefined behavior and thus there will be no guarantees for the behavior of a program that contains it. However, a specific compiler/linker may choose to define a specific behavior as described in the answer, and since the standard mentions a common solution, this behavior is not as undefined as other cases of undefined behavior.Abey

© 2022 - 2024 — McMap. All rights reserved.