Why it is not needed to decorate a “const char *” with “extern” when sharing it cross multiple files

While reading C++ books, I learned that to share a const variable/object cross multiple files, we need to define the const variable once in one of the files, and declare it in each source file where it is used. For example,

In file1.cpp, we define a const int variable globally like
extern const int ca = 100;

In file2.cpp where we use it, we declare this variable like
extern const int ca;

The above convention makes perfect sense for me. However, when coming to a const char pointer, we don't need to define it using extern, and it can be shared cross multiple files very well. Why does such a thing happen? For example,

In file1.cpp, we define a const char pointer variable globally like
const char *cstr = "hello";

In file2.cpp where we use it, we declare this variable like
extern const char *cstr;

To make my question more specific, why can cstr be used in file2.cpp even though it is not decorated with "extern" in file1.cpp?

Many thanks
Your first example is incorrect and will not work. The variable must be declared as non-extern at least once. That instantiates it for the linker.

Using extern tells the linker "don't instantiate this variable, it's already instantiated externally".



Think of it like function bodies. You can prototype a function any number of times in any number of cpp files. However the function can (and must) have only 1 body.

Global vars are the same way. You can "prototype" them any number of times with the extern keyword... but you can only (and must) give them one body (non-extern).


EDIT:

Though in general if you are using global variables -- and especially global variables that span multiple cpp files -- you are doing something very, very wrong.
Last edited on
Thank Disch a lot for your reply. However, both the examples are tested and working. Maybe the advance in C++ standard allows such usage. At the end of the message is the sample code that can be compiled with Dev-C++ and the executable can generate the expected results.

To my understanding, the use of "extern" in its definition (i.e., in file1.cpp) is to tells compiler that the variable has external linkage so that its references in other source files can correctly link to it. Otherwise, this global variable would have only internal linkage and compiler could find its definition upon compiling its references in other files.

This type of usage of "extern" is backed in the book "c++ primer (5th edition)" (for details, please refer to Section 2.4 or below)

Quoted from Section 2.4 of the book "c++ primer (5th edition)"
++++++++++++++++++++++++++++
To define a single instance of a const variable, we use the keyword extern on
both its definition and declaration(s):
Click here to view code image
// file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_1.h
extern const int bufSize; // same bufSize as defined in file_1.cc
++++++++++++++++++++++++++++

Sample source code
file1.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdlib.h>
#include <stdio.h>
#include <iostream>

using namespace std;

const char *pstr = "hello";
extern const int ca = 100;

void print();

int main()
{
	cout << pstr << endl;
	cout << ca << endl;
	
	print();

    return 0;
}


file2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdlib.h>
#include <stdio.h>
#include <iostream>

using namespace std;

extern const char *pstr;
extern const int ca;

void print()
{
	cout << pstr << endl;
	cout << ca << endl;
}


The above code can be compiled in IDE Dev-C++ with TDM-GCC 4.8.1 64-bit Release as well as options "-static-libgcc -std=gnu++11" and below is the output.

==== output ====
hello
100
hello
100

Just node that the above code is for illustration purposes and it is highly appreciated if any inappropriate use could be pointed out.
In file1.cpp,

const char *pstr = "hello"; is a *definition* (because it does not have the keyword extern) of a global variable pstr, which is given external linkage (because it is a non-const member (non-const pointer to const char) of a namespace with external linkage, in this case the global namespace)

extern const int ca = 100; is a *definition* (even though it has the keyword extern it also has an initializer) of an int called 'ca', which is given external linkage (because of the keyword extern)

nm for me shows
0000000000000008 R ca
0000000000000000 D pstr

(R is for read-only data section, D is for the initialized data section)

in file2.cpp, both pstr and ca are introduced by declarations that aren't definitions (because of keyword extern and lack of initializer).
nm shows
U ca
U pstr


To answer your first question
when coming to a const char pointer, we don't need to define it using extern, and it can be shared cross multiple files very well.

The difference is that ca is const and pstr is not (it's a non-const pointer to a const character). Const members of a namespace get internal linkage (unless declared with the keyword extern), non-const members get external linkage (the keyword extern can be used but would be redundant).

Normally people don't use extern on definitions and write something like "const int ca = 100;" in the source file and extern const int ca; in the header that's included in both the file where it's defined and in the file where it's used. Or just const int ca = 100; in the header file, so that each .o gets its own internal linkage version of the constant.
Last edited on
Thanks Cubbi for your crystal clear explanation. Everything now makes sense. The reason for not using a header file is that it will not expose such a question because everything is decorated with "extern" in the header file. The lesson I learned is that
"const char *ptr = "hello" defines a non-const pointer but does "const char * const ptr = "hello"."

Many thanks again.
I learned is that
"const char *ptr = "hello" defines a non-const pointer but does "const char * const ptr

It really is non-const, you can assign something different to it if you want:

1
2
3
4
5
6
7
8
9
#include <iostream>
extern const char *pstr;
void print2()
{
    ++pstr; // changes file1.o's pstr
    std::cout << pstr << '\n'; // prints "ello"
    pstr = "good-bye"; // changes file1.o's pstr again
    std::cout << pstr << '\n';
}
Last edited on
const char *ptr = "hello" or char const *ptr = "hello" defines mutable pointer to constant data.
char * const ptr declares constant pointer to mutable data.
const char * const ptr = "hello" or char const * const ptr = "hello" defines constant pointer to constant data
Topic archived. No new replies allowed.