0

Disclaimer:

I am not interested in opinions about whether the following style is "good practice" or not. I'm searching an answer / a way to get around the problem.

Problem:

I am compiling a class within a cpp file. The compile process strips away my method definitions, that it thinks are "unused". Is there any way to prevent that? (See example below Development Environment)

Development Environment:

I'm using g++ --std=c++98 at the moment. A perfect solution is would be available on all c++98-standard compilers and newer.

Example:

example1.cpp

#include <stdio.h> 

class Example1{
    public: 
    void print(){printf("helloWorld");};

};

Compiling,assemble and link that file does not create the print() method. (g++ --std=c++98 example1.cpp main.cpp, where main.cpp simply calls the print method.)

Compiling this (no linking, g++ --std=c++98 -S ) will output:

example1.s

.file   "example1.cpp"
    .text
    .ident  "GCC: (Ubuntu 9.2.1-9ubuntu2) 9.2.1 20191008"
    .section    .note.GNU-stack,"",@progbits
    .section    .note.gnu.property,"a"
    .align 8
    .long    1f - 0f
    .long    4f - 1f
    .long    5
0:
    .string  "GNU"
1:
    .align 8
    .long    0xc0000002
    .long    3f - 2f
2:
    .long    0x3
3:
    .align 8
4:

There is clearly no print() method generated here.

Not nice Workaround

I can make the compiler generate the method, if I use it in the same file (see code below). However, I'm searching for a nicer solution. Maybe compiler options?

example1.cpp

#include <stdio.h> 

class Example1{
    public: 
    void print(){printf("helloWorld");};

};

// Create a function outside the class, that uses print().
// That way, print will be generated.
void foo(){
    Example1 ex;
    ex.print();
}

Remarks (edit):

  • Defining methods outside the class is no option.
  • Example1 is used in main.cpp as follows:

main.cpp

class Example1{
    public:        
        void print();
};

int main(){
    Example1 example;

    example.print();

    return 0;
}    
DarkTrick
  • 2,447
  • 1
  • 21
  • 39
  • 2
    What real problem does this non-observable optimization causes? – Sam Varshavchik Dec 11 '19 at 04:02
  • 1
    Defining `print` in-class in `example3.cpp`, but not the exact same in `main`, causes undefined behavior. It is an ODR violation no matter what. – walnut Dec 11 '19 at 05:04
  • 1
    Does this answer your question? [Possible to call inline functions in gdb and/or emit them using GCC?](https://stackoverflow.com/questions/22029834/possible-to-call-inline-functions-in-gdb-and-or-emit-them-using-gcc). Note however that even if you make gcc emit the inline function in one translation unit, using it through the `main` translation unit the way you are showing it is still undefined behavior and may or may not "work". – walnut Dec 11 '19 at 07:28
  • *"whether the following style is "good practice""*. As it leads to UB, it is not a style... You have to fix UB (ODR violation) first. – Jarod42 Dec 11 '19 at 09:37
  • @walnut: Thank you for pointing out the other questions. Its answer indeed solves my problem. But (as you point out) it might cause undefined behavior. Some kind of "uninline" keyword would be really nice here, I guess. – DarkTrick Dec 11 '19 at 10:21

2 Answers2

2

The short answer is "you can't with standard C++".

The longer answer comes in a few parts.

Firstly, your workaround does not achieve what you think it does.

#include <stdio.h> 

class Example1{
      public: 
      void print(){printf("helloWorld");};
};

// Create a function outside the class, that uses print().
// That way, print will be generated.
void foo(){
    Example1 ex;
    ex.print();
}

This doesn't prevent the compiler from "removing" the member function Example1::print(). There is nothing preventing the compiler from inlining the call of Example1::print() within foo(), and not (to use your wording) generate that function in a form that (say) can be resolved by a linker.

Second, your "main.cpp"

class Example1{
     public:        
     void print();
 };

int main(){
      Example1 example;

    example.print();

    return 0;
}    

introduces undefined behaviour if it is used in the same project as either version of your "example1.cpp" because the definitions of the class Example1 are observably different - one defines the member function print() within the class definition, and the other doesn't. This breaks the one-definition rule, since your program has two distinct definitions of class Example1.

If your second "example1.cpp" seems to "work" (however you have assessed that) then you just got lucky. The nature of undefined behaviour means that it can seem to work. The problem is, the C++ standard doesn't give any such guarantee - when behaviour is undefined, a compiler is permitted to do what it likes - your code can seem to "work", or it may do something completely different.

The way this sort of thing is usually addressed is to place the definition of class Example1 in a separate header file, such as

// example1.h
#include <stdio.h>
class Example1{
      public: 
      void print(){printf("helloWorld");};
};

and then include that header in all source files that need it. For example;

//   example1.cpp

#include "example1.h"

void foo()
{
    Example1 ex;
    ex.print();
}

and

// main.cpp
#include "example1.h"
int main()
{
    Example1 example;
    example.print();
    return 0;
}

The above two source files (example1.cpp and main.cpp) can then be used safely in the same program.

Since the function Example1::print() is defined inline, the compiler is free to inline it. The compiler is still not required to inline, but it is permitted to. Which means, the generated object files or executable does not necessarily contain anything recognisable (e.g. by examining assembly) as the function Example1::print(). There may only be, in your example, code in main() and foo() that calls printf() but nothing that is distinctly a function named Example1::print().

As another answer (which you rejected, and it was subsequently deleted) said the only way you can prevent inlining is to define the function outside the class definition. That function definition needs to be in exactly one compilation unit in your project. This allows the compiler to "generate" the function, but does not technically require it (a smart compiler and linker are permitted to "remove" that function still).

Peter
  • 35,646
  • 4
  • 32
  • 74
  • I was thinking of "one is a forward declaration of the class and one is its implementation". Apparently it does not work that way. – DarkTrick Dec 11 '19 at 11:08
  • Indeed. A "forward declaration" (like `class Example1;`) is something different entirely. And is not a solution to what you're looking for - since it (unlike a class definition) doesn't provide the compiler with any information about the members of `Example1` at all. – Peter Dec 11 '19 at 11:24
  • If I understand the ODR correctly: "A type definition can only be once per translation unit". As main.cpp and example1.cpp are two different translation units, I would say the double definition should be ok(?). Can you say where I am wrong? Does the problem lies in the fact, that example1.cpp defines `print` differently, that is`inline`? – DarkTrick Dec 11 '19 at 13:52
  • 1
    Where you have gone wrong is, essentially, cherry-picking the standard. C++17 Section 6.2, entitled "One-definition rule" extends over about three pages, and you've summarised (loosely) the first sentence (para 1 of that section). Section 6.2 para 6 deals with the rules related to definitions that are visible to multiple translation units in a program (that para, and its sub-bullet points, extends over a page of the standard) - and that para contradicts your interpretation. – Peter Dec 11 '19 at 20:23
0

Microsoft specific as per Peter's comment

From Here

You can create a false reference to the debugging function that cannot be optimized out. You do this by passing a pointer to the debugging function to a helper function outside your module that doesn’t do anything interesting. Since the helper function is not in your module, the compiler doesn’t know that the helper function doesn’t do anything, so it cannot optimize out the debugging function.

struct ForceFunctionToBeLinked { 
    ForceFunctionToBeLinked(const void *p) { SetLastError(PtrToInt(p)); }
 };

 ForceFunctionToBeLinked forceTestMe(TestMe);
Girspoon
  • 96
  • 5