Inlines with external linkage

R

Richard Hayden

Hi,

I have the following code:

/******************************** file1.c
#include <iostream>

extern void dummy();

inline int testfunc() {
return 1;
}

int main(int argc, char** argv) {
std::cout << testfunc();
dummy();

return 0;
}
/****************************************

/******************************** file2.c
#include <iostream>

inline int testfunc() {
return 2;
}

void dummy() {
std::cout << testfunc();
}
/****************************************

Now, I wasn't sure exactly what this code should do (by 'this code', I
mean the program obtained by compiling these two files and then linking
their respective object files). I was thinking it would output 12 or
there'd be an error of some description.

However, depending on the order of linkage, it succeeds and outputs 11
or 22. In other words, the linker is somehow removing multiple
definitions of this inline function and resolving both 'calls' to one of
them... but surely if it's inline, it will be dealt with at compile time
and the linker should have nothing to do with it? I know that 'inline'
doesn't guarantee inlining, but surely the linker can't decide not to
inline it, since surely that decision must be made at compile time. It
only behaves as expected if I force internal linkage on the testfuncs
with the 'static' keyword. There's something going on, because the
linker obviously 'doesn't mind' that this function has been multiply
defined, which I assume is because the compiler needed to have a
definition in each translation unit in order to be inlined it. I'm
confused though how the linker is resolving these multiple definitions
though. Any insight into inlines and external linkage etc. would be
greatly appreciated.

FYI, I'm using g++.

Thanks,
 
A

Alf P. Steinbach

* Richard Hayden:
/******************************** file1.c
#include <iostream>

extern void dummy();

inline int testfunc() {
return 1;
}

int main(int argc, char** argv) {
std::cout << testfunc();
dummy();

return 0;
}
/****************************************

/******************************** file2.c
#include <iostream>

inline int testfunc() {
return 2;
}

void dummy() {
std::cout << testfunc();
}
/****************************************

You have Undefined Behavior (TM) because you have two different
definitions of the same external linkage 'inline' function.
 
S

Sumit Rajan

Richard Hayden said:
Hi,

I have the following code:

/******************************** file1.c
#include <iostream>

extern void dummy();

inline int testfunc() {
return 1;
}

int main(int argc, char** argv) {
std::cout << testfunc();
dummy();

return 0;
}
/****************************************

/******************************** file2.c
#include <iostream>

inline int testfunc() {
return 2;
}

void dummy() {
std::cout << testfunc();
}
/****************************************

Now, I wasn't sure exactly what this code should do (by 'this code', I
mean the program obtained by compiling these two files and then linking
their respective object files). I was thinking it would output 12 or
there'd be an error of some description.

However, depending on the order of linkage, it succeeds and outputs 11
or 22.

Undefined behaviour as per 3.2/5.

The One Definition Rule allows you to repeat the definition of an inline
function as long as:
1) They appear in different translation units.
2) The are token-for-token identical and the meanings of these tokens
should be the same in both translation units.
 
R

Richard Hayden

Alf said:
* Richard Hayden:



You have Undefined Behavior (TM) because you have two different
definitions of the same external linkage 'inline' function.

Hi,

Thanks for your reply.

I understand what you mean in that case, so I have amended my code to:

/*******************test1.c*******************/
#include <iostream>

extern void dummy();

inline int testfunc() {
return 1;
}

int main(int argc, char** argv) {
std::cout << testfunc();
dummy();

return 0;
}
/*********************************************/

/*******************test2.c*******************/
#include <iostream>

extern int testfunc();

void dummy() {
std::cout << testfunc();
}
/*********************************************/

Surprisingly, it works as expected, i.e. the output is 11. How is this
occuring? Surely the linker must be doing some of the compiler's work
here in order to inline testfunc in the call in test2.c?

Thanks,
 
A

Alf P. Steinbach

* Richard Hayden:
I understand what you mean in that case, so I have amended my code to:

/*******************test1.c*******************/
#include <iostream>

extern void dummy();

inline int testfunc() {
return 1;
}

int main(int argc, char** argv) {
std::cout << testfunc();
dummy();

return 0;
}
/*********************************************/

/*******************test2.c*******************/
#include <iostream>

extern int testfunc();

void dummy() {
std::cout << testfunc();
}
/*********************************************/

Well this is simply invalid code. But it's fine details; I was
surprised when I saw the combination of 'inline' one place and
'extern' another place and had to look it up. §3.3/3: "An inline
function shall be defined in every translation unit in which it
is used", which is repeated in §7.1.2/4 which continues: "and
shall have exactly the same definition in every case. ... If a
function with external linkage is declared inline in one
translation unit, it shall be declared inline in all translation
units in which it appears; no diagnostic is required".
^^^^^^^^^^^^^^^^^^^^^^^^^

The extern'ness of inline functions is simply that they are (or can
be) visible to the linker so the linker can remove all but one
occurrence -- and you don't know which one, so must be identical.

Surprisingly, it works as expected, i.e. the output is 11.

Still UB...


How is this
occuring? Surely the linker must be doing some of the compiler's work
here in order to inline testfunc in the call in test2.c?

See above. But with your code it isn't required to. Because it's UB.
 
J

JKop

Alf P. Steinbach posted:
* Richard Hayden:

It isn't, and AFAIK never was.


I was implying that all inline functions have internal linkage, ie. as if
they have "static" before them. They should all be defined in header files
if they're to be used in more than one translation unit.

-JKop
 
A

Alf P. Steinbach

* JKop:
* Alf P. Steinbach posted:

I was implying that all inline functions have internal linkage, ie. as if
they have "static" before them.

That is not correct.

In particular, the address of an inline function with external linkage
(the default) is the same in all translation units.

RTFM.
 
J

JKop

Alf P. Steinbach posted:
That is not correct.

In particular, the address of an inline function with external linkage
(the default) is the same in all translation units.

RTFM.


An inline function doesn't have an address. Here's how inline functions
work.


inline void Poo(int& k)
{
k = 4;
}

int main()
{
int p = 7;

Poo(p);
}


This becomes:


int main()
{
int p = 7;

p = 4;
}



So pretty please could you explain to me how in the hell an inline function
could have an address in memory? I'm all ears.


-JKop
 
A

Andrey Tarasevich

Richard said:
...
I understand what you mean in that case, so I have amended my code to:

/*******************test1.c*******************/
#include <iostream>

extern void dummy();

inline int testfunc() {
return 1;
}

int main(int argc, char** argv) {
std::cout << testfunc();
dummy();

return 0;
}
/*********************************************/

/*******************test2.c*******************/
#include <iostream>

extern int testfunc();

void dummy() {
std::cout << testfunc();
}
/*********************************************/

Surprisingly, it works as expected, i.e. the output is 11. How is this
occuring? Surely the linker must be doing some of the compiler's work
here in order to inline testfunc in the call in test2.c?
...

What you do here is basically a hack. Since inline functions have
external linkage by default (just like any other function) the compiler
has to take certain steps to ensure that expression
'&function_name_here' evaluates to the same value in every translation
unit where it is used. In typical implementation the burden of
satisfying this requirement is placed on the linker. For this reason,
the compiler has to include inline functions into linker tables, just
like non-inline functions. In your code sample above you exploited the
fact that inline function is present in the linker tables by trying to
link to that function from another translation unit. It is illegal from
the language specification point of view (Alf has already explained why)
but it might work in practice (as a hack). In my opinion,
implementations should use different name mangling formats for inline
functions to prevent such code from compiling.
 
A

Andrey Tarasevich

JKop said:
...
So pretty please could you explain to me how in the hell an inline function
could have an address in memory? I'm all ears.
...

Firstly, the bottom line is that at the language level any function has
address in memory.

Secondly, you are mixing two different and completely independent
things. One thing as "an inline/non-inline function". Another thing is
"an inlined/non-inlined call to a function". Nothing prevents the
compiler from generating a "regular" (non-inline) body for the function
(so that it has an address) and at the same time inline it whenever
possible. Remember also that the decision to inline a function can be
made on per-call basis, not on per-function basis. I.e. some calls to an
inline functions might get inlined, while other calls to the same
function might get directed to a "regular" body of that function.

Thirdly, nothing prevents the compiler from treating inline functions as
non-inline ones _and_ _vice_ _versa_. I.e. nothing prevents the compiler
from inlining non-inline functions. Formally speaking, at the language
level specifier 'inline' doesn't mean _anything_ besides that exception
from ODR specific to inline functions ("must be defined in every
translation unit..." etc.). The rest is up to the implementation.

--
Best regards,
Andrey Tarasevich

P.S. I bet you don't understand what sense does declaring a member
function as "virtual inline" make. Try googling for it, it's been beaten
do death.
 
H

Howard

JKop said:
Andrey Tarasevich posted:


You're educated. I wish I could make statements and provide support just
like you.

Well, you can, and you did! :) You made an incorrect statement, and
provided nothing to back it up. (See Alf's posts for the actual facts of
the case.)

-Howard
 
J

JKop

Okay, I'm trying to get my head around this. Consider the following header
file, "yum.hpp":

#ifndef INCLUDE_YUM_HPP
#define INCLUDE_YUM_HPP

inline int Yum(char k)
{
return k - 5;
}

#endif


Now, consider that this function is called... inlinedly... in every instance
that it's called throughout the entire program, like so:

A.cpp

#include "yum.hpp"

int blah(void)
{
return Yum('s');
}


B.cpp

#include "yum.hpp"

int poo(char* n)
{
return Yum(*n);
}


So there's no problem at all with that.

But consider that there's just 3 instances in the program where it's
called... outlinedly. Each of these three callings are from 3 different
source files, 3 different translation units. The "problem" here is that this
fully-fledged function will be in memory not once, twice, but thrice. 3
copies of it in memory - that's a waste. If I were to specify external
linkage (and how would I?) for this function, then the linker would start
complaining because I'd have a multiple definition for each of the
translation units that include my "yum.hpp".


So here's my question: How to you get the function to be inline... yet...
where there's instances where it's called outlinedly, that there's only one
copy of it in memory, as opposed to 3.


Any explanations apprecitated.

(Just for more substance, the 3 instances where it's called outlinedly is
where something wants a pointer to a function).


-JKop
 

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,995
Messages
2,570,236
Members
46,825
Latest member
VernonQuy6

Latest Threads

Top