3

I have found that I could achieve the desired results without using extern (though I do agree that it gives reader some kind of an hint about the variable). In some cases using extern gave undesired results.

xyz.h

int i;

file1.c

#include "xyz.h"
....
i=10;
....

file2.c

#include "xyz.h"
main()
{
printf("i=%d\n",i);
}

Of course, it was a large project, broke it down for simple understanding. With extern keyword, I couldnt get desired results. In fact, I got linker error for the variable i with "extern" approach.

Code with "extern" approach,

file1.c

int i;
main()
{
i=10;
}

file2.c

extern int i;
foo()
{
printf("i=%d\n",i);
}

This gave linker error. I just wanted to know why it worked in the first case and also the practical case where we cannot do it without using the keyword "extern". Thanks.

Harsha
  • 323
  • 1
  • 17
  • In the second case, you have two functions called `main`. – Seva Alekseyev Oct 06 '16 at 01:23
  • http://port70.net/~nsz/c/c11/n1570.html#6.2.2 – too honest for this site Oct 06 '16 at 01:25
  • Apologies. file2.c doesnt have main() – Harsha Oct 06 '16 at 01:26
  • The problem with your code is that it contains *two* definitions of the same entity, so it is not well-formed. *That*'s why you need `extern`. – Kerrek SB Oct 06 '16 at 01:33
  • @Kerrek: If you are referring to 1st approach then I would like to inform you that it worked in my case. With extern approach, I got Linker error for variable i – Harsha Oct 06 '16 at 01:36
  • 1
    Um... How is the linked "duplicate" really a duplicate. The OP's question is about *avoiding* `extern`, while the alleged duplicate is about proper use of `extern`. – AnT stands with Russia Oct 06 '16 at 01:40
  • 1
    @Harsha: I don't see how you could possibly get a linker error for your second approach. Once you fixed the function name, your second approach no longer has any linker issues. The only way to get linker errors from it is to forget to feed one of the object files to the linker. – AnT stands with Russia Oct 06 '16 at 01:41
  • @AnT: Thanks. That was my exact point. – Harsha Oct 06 '16 at 01:41
  • @AnT: As I said it is a project with multiple files. Broke it down for you guys. – Harsha Oct 06 '16 at 01:43
  • @Harsha: I tried it, too, and I get multiple definition errors. With `extern` you do of course have to provide a definition *somewhere* still. – Kerrek SB Oct 06 '16 at 01:46
  • Reopened because this is clearly not a duplicate of the question it was marked as duplicate of, even if they have related answers. – R.. GitHub STOP HELPING ICE Oct 06 '16 at 01:49
  • Are you compiling each file separately? That could give you a linker error (for file2) if you aren't linking both object files... – Dmitri Oct 06 '16 at 02:02
  • @Dmitri: Yes, I think you are right. I am no expert in 'make'. In this case, would it work with my first approach. As I see it, it is working.. – Harsha Oct 06 '16 at 02:06

2 Answers2

7

Formally, your first program is invalid. Defining a variable in header file and then including this header file into multiple translation units will ultimately result in multiple definitions of the same entity with external linkage. This is a constraint violation in C.

6.9 External definitions

5 An external definition is an external declaration that is also a definition of a function (other than an inline definition) or an object. If an identifier declared with external linkage is used in an expression (other than as part of the operand of a sizeof operator whose result is an integer constant), somewhere in the entire program there shall be exactly one external definition for the identifier; otherwise, there shall be no more than one.

The definition of i in your first example is a tentative definition (as it has been mentioned in the comments), but it turns into a regular full fledged external definition of i at the end of each translation unit that includes the header file. So, the "tentativeness" of that definition does not change anything from the "whole program" point of view. It is not really germane to the matter at hand (aside for a little remark below).

What makes your first example to compile without error is a popular compiler extension, which is even mentioned as such in the language standard.

J.5 Common extensions

J.5.11 Multiple external definitions

1 There may be more than one external definition for the identifier of an object, with or without the explicit use of the keyword extern; if the definitions disagree, or more than one is initialized, the behavior is undefined (6.9.2).

(It is quite possible that what originally led to that compiler extension in C is some implementational peculiarities of tentative definition support, but at abstract language level tentative definitions have nothing to do with this.)

Your second program is valid with regard to i (BTW, implicit int is no longer supported in C). I don't see how you could get any linker errors from it.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • 1
    `int i;` a t file-scope is just a _tentative definition_, not an actual definition. – too honest for this site Oct 06 '16 at 01:25
  • @Olaf: Nonetheless the tentative definition becomes actual in both TUs, doesn't it? – Kerrek SB Oct 06 '16 at 01:26
  • 1
    @Olaf: Yes, but this is completely besides the point. Tentative definition becomes regular definition at the end of the translation unit. And having the same external object defined in two different translation units is as illegal in C as it is in C++. – AnT stands with Russia Oct 06 '16 at 01:27
  • 1
    @KerrekSB: I'm just trying to figure out from the standard. I'm actually not sure they don't reference **the same** object. IIRC, `extern` indeed is unnecessary. It just is good practice to avoid an actual definition on a typo. (Never thought much about what exactly the standard tells, as I just `extern` for the reason above). – too honest for this site Oct 06 '16 at 01:28
  • @AnT: I have changed file2.c. It doesn't have main(). Apologies for that – Harsha Oct 06 '16 at 01:31
  • I think 6.2.2p5 and 6.9.2p2 are relevant here. – too honest for this site Oct 06 '16 at 01:32
  • @Olaf: It does not need to be normative at all. I quote it just as a curious detail (which also serves as another confirmation that my interpretation of normative text is correct). The point made in my answer does not in any way rely on J. – AnT stands with Russia Oct 06 '16 at 01:34
  • @KerrekSB: It's too late here for language-lawyer. I just found those two section which seem to support my objection. I'd appreciate any comment (promise to read that tomorrow:-) – too honest for this site Oct 06 '16 at 01:35
  • @AnT: I think it is well defined, but ... - well - read my other comments. (Sorry, there is a point I cannot argue at that level in English anymore). – too honest for this site Oct 06 '16 at 01:36
  • @Olaf: Um.. I still don't understand why you attempt to bring tentative definitions into the picture. Tentative definition is a concept that is completely local to its translation unit. It does not "protrude" into the external world in any way. It has no external effects whatsoever. Meanwhile, this question is exclusively about the external effects: it's about whole program linkage. Tentative definitions can't possible play any role here. – AnT stands with Russia Oct 06 '16 at 01:38
  • @AnT: It is related, but - as I wrote, I don't have the concentration for this today. If you want to know this, please try yourself. I just tried to show up some traces I remembered. Also see the examples: http://port70.net/~nsz/c/c11/n1570.html#6.9.2p4 . As I read them `i4` very well designates the same object, if used in multiple TUs. Which means the linker will do the final resolution of tentative to final definitions. – too honest for this site Oct 06 '16 at 01:41
  • @Olaf: The example you linked is, again, an example of tentative definition. Tentative definition is an interesting feature in C, but it only works locally within confines of a single translation unit. That's exactly what the linked example illustrates. This feature does not in any way provide you with the ability to scatter definitions of the same object across multiple translation units. 6.9/5 explicitly states that you can't do that. – AnT stands with Russia Oct 06 '16 at 01:47
  • @AnT: Whatever. I'm too done to discuss. You win (for now) – too honest for this site Oct 06 '16 at 01:51
  • @AnT, you're right, but I think you've phrased it confusingly. A tentative definition does not *directly* affect the world outside its TU, but the presence of one in a TU may cause the TU to be treated as if it contained a full-fledged external definition of the same identifier (6.9.2/2). Since there can be at most one external definition of each identifier in a whole program (6.9/5), that certainly has an external effect, but that indeed does not provide a way to have multiple external definitions of the same identifier in the same (conforming) program. – John Bollinger Oct 06 '16 at 02:06
  • @JohnBollinger: As far as I understand the examples and the text, you can have multiple `int i;` tentative declarations in a TU (see `i4` in the example). If multiple TU have such declarations, they all designate the same object, because of the external linkage. Thus actually the linker resolves these tentative definitions finally. – too honest for this site Oct 06 '16 at 12:34
  • @John Bollinger: I was just trying to find a way to modify the standard wording in order to *emphasize* the fact that the *"tentative-ness"* of the definition is not supposed to escape the confines of a TU. As seen from the outside, all external objects defined in a TU are the same, regardless of whether they were defined by a tentative definition or by a regular (non-tentative) definition. The *"tentative-ness"* of a tentative definition has no external effect, while the *"defintion-ness"* of a tentative definition certainly does. – AnT stands with Russia Oct 06 '16 at 15:07
  • In the real world implementations though it appears that the *"tentative-ness"* is allowed to "leak" to the outside world. – AnT stands with Russia Oct 06 '16 at 15:09
1

There are at least 2 cases where extern is meaningful and not "redundant":

  1. For objects (not functions) at file scope, it declares the object with external linkage without providing a tentative definition; tentative definitions turn into full definitions at the end of a translation unit, and having the same identifier defined with external linkage in multiple translation units is not permitted.

  2. At block scope (in a function), extern allows you to declare and access an object or function with external linkage without bringing the identifier into file scope (so it's not visible outside the block with the declaration). This is useful if the name could conflict with something else at file scope. For example:

    static int a;
    int foo(void)
    {
        return a;
    }
    int bar(void)
    {
        extern int a;
        return a;
    }
    

    Without the extern keyword in bar, int a; would yield a local variable (automatic storage).

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711