I have this piece of code:
constexpr static VOID fStart()
{
auto a = 3;
a++;
}
__declspec(naked)
constexpr static VOID fEnd() {};
static constexpr auto getFSize()
{
return (SIZE_T)((PBYTE)fEnd - (PBYTE)fStart);
}
static constexpr auto fSize = getFSize();
static BYTE func[fSize];
Is it possible to declare "func[fSize]" array size as the size of "fStart()" during compilation without using any std library? It is necessary in order to copy the full code of fStart() into this array later.
There is no method in standard C++ to get the length of a function.
You'll need to use a compiler specific method.
One method is to have the linker create a segment, and place your function in that segment. Then use the length of the segment.
You may be able to use some assembly language constructs to do this; depends on the assembler and the assembly code.
Note: in embedded systems, there are reasons to move function code, such as to On-Chip memory or swap to external memory, or to perform a checksum on the code.
The following calculates the "byte size" of the fStart function. However, the size cannot be obtained as a constexpr this way, because casting loses the compile-time const'ness (see for example Why is reinterpret_cast not constexpr?), and the difference of two unrelated function pointers cannot be evaluated without some kind of casting.
#pragma runtime_checks("", off)
__declspec(code_seg("myFunc$a")) static void fStart()
{ auto a = 3; a++; }
__declspec(code_seg("myFunc$z")) static void fEnd(void)
{ }
#pragma runtime_checks("", restore)
constexpr auto pfnStart = fStart; // ok
constexpr auto pfnEnd = fEnd; // ok
// constexpr auto nStart = (INT_PTR)pfnStart; // error C2131
const auto fnSize = (INT_PTR)pfnEnd - (INT_PTR)pfnStart; // ok
// constexpr auto fnSize = (INT_PTR)pfnEnd - (INT_PTR)pfnStart; // error C2131
On some processors and with some known compilers and ABI conventions, you could do the opposite:
generate machine code at runtime.
For x86/64 on Linux, I know GNU lightning, asmjit, libgccjit doing so.
The elf(5) format knows the size of functions.
On Linux, you can generate shared libraries (perhaps generate C or C++ code at runtime (like RefPerSys does and GCC MELT did), then compiling it with gcc -fPIC -shared -O) and later dlopen(3) / dlsym(3) it. And dladdr(3) is very useful. You'll use function pointers.
Read also a book on linkers and loaders.
But you usually cannot move machine code without doing some relocation, unless that machine code is position-independent code (quite often PIC is slower to run than ordinary code).
A related topic is garbage collection of code (or even of agents). You need to read the garbage collection handbook and take inspiration from implementations like SBCL.
Remember also that a good optimizing C++ compiler is allowed to unroll loops, inline expand function calls, remove dead code, do function cloning, etc... So it may happen that machine code functions are not even contiguous: two C functions foo() and bar() could share dozens of common machine instructions.
Read the Dragon book, and study the source code of GCC (and consider extending it with your GCC plugin). Look also into the assembler code produced by gcc -O2 -Wall -fverbose-asm -S. Some experimental variants of GCC might be able to generate OpenCL code running on your GPGPU (and then, the very notion of function end does not make sense)
With generated plugins thru C and C++, you carefully could remove them using dlclose(3) if you use Ian Taylor's libbacktrace and dladdr to explore your call stack. In 99% of the cases, it is not worth the trouble, since in practice a Linux process (on current x86-64 laptops in 2021) can do perhaps a million of dlopen(3), as my manydl.c program demonstrates (it generates "random" C code, compile it into a unique /tmp/generated123.so, and dlopen that, and repeat many times).
The only reason (on desktop and server computers) to overwrite machine code is for long lasting server processes generating machine code every second. If this was your scenario, you should have mentioned it (and generating JVM bytecode by using Java classloaders could make more sense).
Of course, on 16 bits microcontrollers things are very different.
Is it possible to calculate function length at compile time in C++?
No, because at runtime time some functions do not exist anymore.
The compiler have somehow removed them. Or cloned them. Or inlined them.
And for C++ it is practically important with standard containers: a lot of template expansion occurs, including for useless code which has to be removed by your optimizing compiler at some point.
(Think -in 2021 of compilation with a recent GCC 10.2 or 11. using everywhere, and linking with, gcc -O3 -flto -fwhole-program: a function foo23 might be defined but never called, and then it is not inside the ELF executable)
Related
I have the following piece of code which is a part of api (cdecl). In MSVC++ the sizeof bool is 1 byte, but since bool is implementation defined, some programs compiled by other compiler/the author incorrectly define function signature may treat bool as >1 byte and calling the check below may return true on their side of programs.
virtual bool isValid()
{
return false;
// ^ code above in asm: xor al, al
}
To avoid this, I put an inline asm, xor eax, eax before the return - but I feel it a bit hacky and it of course will not work on x64 due to lack of inline assembler support.
Using #define bool int will work but it is not I wanted, as I have structs that have bool datatype inside it and using this will causes corruption.
Is there anything like intrinsics that can zeroed the eax/rax register or anything that can solve this problem?
There's nothing that will do what you're asking for. Your problem needs a much different solution.
First any code that "incorrectly define function signature" is broken an needs to fixed. It's never the solution to work around it in other code.
Next your problem is like more than just bool being implementation defined, the C++ standard makes a whole host of things are implementation defined. So much so that two different C++ compilers are rarely have a compatible ABIs. If your code provides C++ interfaces for the use of code compiled by other people you'll probably need to produce separately compiled binaries, whether in the form of object files, static libraries, DLLs or executables, for each different compiler you want to support. In fact you may need to provide separate binaries for each version of each compiler.
There are two C++ compilers the try to be compatible with the Microsoft C++ ABI. The first is Intel's C++ compiler and the second is the Windows port of clang. The clang implementation is notably still a work in progress. You may still need to create separate versions for each version of the Microsoft C/C++ runtime libraries your code is compiled with.
You can potentially reduce the number of different versions of binaries that you need to distribute by providing a pure C interface to your code. A pure C interface means using only C data types and only functions declared as extern "C". While things like classes, member functions, templates, RTTI and exceptions can be used in your implementation the can't be used as part of your public interface. An exception are COM-like interfaces, classes with nothing but public pure virtual functions. Since C compilers for Windows all use essentially the same C ABI and support COM interfaces, compatibility issues are less likely to be an issue. However the bool type (actually the _Bool type in C) is probably not safe to use, since it's a relatively recent addition to the C language. Use int in your C interfaces instead.
Note that because of C/C++ runtime differences even if you all you want to do distribute compiled binaries for use with Microsoft's Visual C++ compiler you may still need to distribute versions for each version of the compiler. That's because each version comes with a different runtime implementation and which have data structures with incompatible internal layouts. You can't pass an STL container created in a function compiled by one version of Visual C++ to a function compiled with a different version. You can't allocate memory with malloc in an executable and free it in a DLL, if the executable and DLL use different versions of the C runtime.
Unfortunately unless you're willing to restrict your users to one particular compiler the easy solution to your problem that you're looking for may not exist. Note that this is a common solution used by programs that provide plugin support. Pugins need to be compiled the same version of the same compiler that compiled the executable.
Consider following program:
#include <iostream>
struct Test
{
int& ref1;
int& ref2;
int& ref3;
};
int main()
{
std::cout<<sizeof(Test)<<'\n';
}
I know that C++ compiler can optimize the reference variables entirely so that they won't take any space in memory at all.
I tested a above demo program to see the output.
But when I compile & run on g++ 4.8.1 it gives me output 12.
It looks like compiler isn't optimizing the reference variables. I was expecting size of the Test struct to be 1.
I've also used -Os command line option but it still gives me output 12. I have also tried this program on MSVS 2010
compiled with /Ox command line option but it looks like Microsoft compiler isn't performing any optimization at all.
The three reference variables are unused & they aren't associated with any other variable. Then why compilers aren't optimizing them?
The size of the struct stays the same, there is nothing to optimize. If you would like to create an array of Test it should allocate the right size for each Test. The compiler cannot know which will be used or not. That's why there is no such optimization.
Unused variables would be for example a new int& int inside your main function. If this is unused, the optimizer will optimize it away.
Theoretically, if the world would only consist of simple programs, the compiler could optimize the sizeof of this struct to 1, because the sizeof of a struct is unspecified.
But in our real world, we have separate compilation of shared libraries that the compiler when compiling your code has no clue about (for example you could LoadLibrary or dlopen) that also happen to define your struct and where the sizeof should better agree with that in your program.
So actually a compiler better doesn't opimize the sizeof to 1 :)
In 8.3.2.4, of the C++ standard, it is said
It is unspecified whether or not a reference requires storage
So, the standard leaves it open to the implementation how references should be implemented. This implies that the size of your struct can be non-zero.
If the compiler would remove the references from the struct, you would not be able to link code compiled with different compiler settings. Imagine you compile one translation unit with optimizations, the other one without and link them together and pass an object from one TU to the other. Which size should the code assume? A function in TU 1 allocates 12 bytes on the stack, while a function in TU 2 allocates some other space.
The compiler can optimize your program and e.g. remove temporary objects, assignments etc. It may that you create an object of your struct somewhere in your source code and use it, but it will not be seen in the assembler code because it is not needed. What compilers also frequently do is remove indirections, e.g. by replacing references with direct access.
GCC, MSVC, LLVM, and probably other toolchains have support for link-time (whole program) optimization to allow optimization of calls among compilation units.
Is there a reason not to enable this option when compiling production software?
I assume that by "production software" you mean software that you ship to the customers / goes into production. The answers at Why not always use compiler optimization? (kindly pointed out by Mankarse) mostly apply to situations in which you want to debug your code (so the software is still in the development phase -- not in production).
6 years have passed since I wrote this answer, and an update is necessary. Back in 2014, the issues were:
Link time optimization occasionally introduced subtle bugs, see for example Link-time optimization for the kernel. I assume this is less of an issue as of 2020. Safeguard against these kinds of compiler and linker bugs: Have appropriate tests to check the correctness of your software that you are about to ship.
Increased compile time. There are claims that the situation has significantly improved since 2014, for example thanks to slim objects.
Large memory usage. This post claims that the situation has drastically improved in recent years, thanks to partitioning.
As of 2020, I would try to use LTO by default on any of my projects.
This recent question raises another possible (but rather specific) case in which LTO may have undesirable effects: if the code in question is instrumented for timing, and separate compilation units have been used to try to preserve the relative ordering of the instrumented and instrumenting statements, then LTO has a good chance of destroying the necessary ordering.
I did say it was specific.
If you have well written code, it should only be advantageous. You may hit a compiler/linker bug, but this goes for all types of optimisation, this is rare.
Biggest downside is it drastically increases link time.
Apart from to this,
Consider a typical example from embedded system,
void function1(void) { /*Do something*/} //located at address 0x1000
void function2(void) { /*Do something*/} //located at address 0x1100
void function3(void) { /*Do something*/} //located at address 0x1200
With predefined addressed functions can be called through relative addresses like below,
(*0x1000)(); //expected to call function2
(*0x1100)(); //expected to call function2
(*0x1200)(); //expected to call function3
LTO can lead to unexpected behavior.
updated:
In automotive embedded SW development,Multiple parts of SW are compiled and flashed on to a separate sections.
Boot-loader, Application/s, Application-Configurations are independently flash-able units. Boot-loader has special capabilities to update Application and Application-configuration. At every power-on cycle boot-loader ensures the SW application and application-configuration's compatibility and consistence via Hard-coded location for SW-Versions and CRC and many more parameters. Linker-definition files are used to hard-code the variable location and some function location.
Given that the code is implemented correctly, then link time optimization should not have any impact on the functionality. However, there are scenarios where not 100% correct code will typically just work without link time optimization, but with link time optimization the incorrect code will stop working. There are similar situations when switching to higher optimization levels, like, from -O2 to -O3 with gcc.
That is, depending on your specific context (like, age of the code base, size of the code base, depth of tests, are you starting your project or are you close to final release, ...) you would have to judge the risk of such a change.
One scenario where link-time-optimization can lead to unexpected behavior for wrong code is the following:
Imagine you have two source files read.c and client.c which you compile into separate object files. In the file read.c there is a function read that does nothing else than reading from a specific memory address. The content at this address, however, should be marked as volatile, but unfortunately that was forgotten. From client.c the function read is called several times from the same function. Since read only performs one single read from the address and there is no optimization beyond the boundaries of the read function, read will always when called access the respective memory location. Consequently, every time when read is called from client.c, the code in client.c gets a freshly read value from the address, just as if volatile had been used.
Now, with link-time-optimization, the tiny function read from read.c is likely to be inlined whereever it is called from client.c. Due to the missing volatile, the compiler will now realize that the code reads several times from the same address, and may therefore optimize away the memory accesses. Consequently, the code starts to behave differently.
Rather than mandating that all implementations support the semantics necessary to accomplish all tasks, the Standard allows implementations intended to be suitable for various tasks to extend the language by defining semantics in corner cases beyond those mandated by the C Standard, in ways that would be useful for those tasks.
An extremely popular extension of this form is to specify that cross-module function calls will be processed in a fashion consistent with the platform's Application Binary Interface without regard for whether the C Standard would require such treatment.
Thus, if one makes a cross-module call to a function like:
uint32_t read_uint32_bits(void *p)
{
return *(uint32_t*)p;
}
the generated code would read the bit pattern in a 32-bit chunk of storage at address p, and interpret it as a uint32_t value using the platform's native 32-bit integer format, without regard for how that chunk of storage came to hold that bit pattern. Likewise, if a compiler were given something like:
uint32_t read_uint32_bits(void *p);
uint32_t f1bits, f2bits;
void test(void)
{
float f;
f = 1.0f;
f1bits = read_uint32_bits(&f);
f = 2.0f;
f2bits = read_uint32_bits(&f);
}
the compiler would reserve storage for f on the stack, store the bit pattern for 1.0f to that storage, call read_uint32_bits and store the returned value, store the bit pattern for 2.0f to that storage, call read_uint32_bits and store that returned value.
The Standard provides no syntax to indicate that the called function might read the storage whose address it receives using type uint32_t, nor to indicate that the pointer the function was given might have been written using type float, because implementations intended for low-level programming already extended the language to supported such semantics without using special syntax.
Unfortunately, adding in Link Time Optimization will break any code that relies upon that popular extension. Some people may view such code as broken, but if one recognizes the Spirit of C principle "Don't prevent programmers from doing what needs to be done", the Standard's failure to mandate support for a popular extension cannot be viewed as intending to deprecate its usage if the Standard fails to provide any reasonable alternative.
LTO could also reveal edge-case bugs in code-signing algorithms. Consider a code-signing algorithm based on certain expectations about the TEXT portion of some object or module. Now LTO optimizes the TEXT portion away, or inlines stuff into it in a way the code-signing algorithm was not designed to handle. Worst case scenario, it only affects one particular distribution pipeline but not another, due to a subtle difference in which encryption algorithm was used on each pipeline. Good luck figuring out why the app won't launch when distributed from pipeline A but not B.
LTO support is buggy and LTO related issues has lowest priority for compiler developers. For example: mingw-w64-x86_64-gcc-10.2.0-5 works fine with lto, mingw-w64-x86_64-gcc-10.2.0-6 segfauls with bogus address. We have just noticed that windows CI stopped working.
Please refer the following issue as an example.
I know there are similar questions to this, but compiling different file with different flag is not acceptable solution here since it would complicate the codebase real quick. An answer with "No, it is not possible" will do.
Is it possible, in any version of Clang OR GCC, to compile intrinsics function for SSE 2/3/3S/4.1 while only enable compiler to use SSE instruction set for its optimization?
EDIT: For example, I want compiler to turn _mm_load_si128() to movdqa, but compiler must not do emit this instruction at any other place than this intrinsics function, similar to how MSVC compiler works.
EDIT2: I have dynamic dispatcher in place and several version of single function with different instruction sets written using intrinsics function. Using multiple file will make this much harder to maintain as same version of code will span multiple file, and there are a lot of this type of functions.
EDIT3: Example source code as requested: https://github.com/AviSynth/AviSynthPlus/blob/master/avs_core/filters/resample.cpp or most file in that folder really.
Here is an approach using gcc that might be acceptable. All source code goes into a single source file. The single source file is divided into sections. One section generates code according to the command line options used. Functions like main() and processor feature detection go in this section. Another section generates code according to a target override pragma. Intrinsic functions supported by the target override value can be used. Functions in this section should be called only after processor feature detection has confirmed the needed processor features are present. This example has a single override section for AVX2 code. Multiple override sections can be used when writing functions optimized for multiple targets.
// temporarily switch target so that all x64 intrinsic functions will be available
#pragma GCC push_options
#pragma GCC target ("arch=core-avx2")
#include <intrin.h>
// restore the target selection
#pragma GCC pop_options
//----------------------------------------------------------------------------
// the following functions will be compiled using default code generation
//----------------------------------------------------------------------------
int dummy1 (int a) {return a;}
//----------------------------------------------------------------------------
// the following functions will be compiled using core-avx2 code generation
// all x64 intrinc functions are available
#pragma GCC push_options
#pragma GCC target ("arch=core-avx2")
//----------------------------------------------------------------------------
static __m256i bitShiftLeft256ymm (__m256i *data, int count)
{
__m256i innerCarry, carryOut, rotate;
innerCarry = _mm256_srli_epi64 (*data, 64 - count); // carry outs in bit 0 of each qword
rotate = _mm256_permute4x64_epi64 (innerCarry, 0x93); // rotate ymm left 64 bits
innerCarry = _mm256_blend_epi32 (_mm256_setzero_si256 (), rotate, 0xFC); // clear lower qword
*data = _mm256_slli_epi64 (*data, count); // shift all qwords left
*data = _mm256_or_si256 (*data, innerCarry); // propagate carrys from low qwords
carryOut = _mm256_xor_si256 (innerCarry, rotate); // clear all except lower qword
return carryOut;
}
//----------------------------------------------------------------------------
// the following functions will be compiled using default code generation
#pragma GCC pop_options
//----------------------------------------------------------------------------
int main (void)
{
return 0;
}
//----------------------------------------------------------------------------
There is no way to control instruction set used for the compiler, other than the switches on the compiler itself. In other words, there are no pragmas or other features for this, just the overall compiler flags.
This means that the only viable solution for achieving what you want is to use the -msseX and split your source into multiple files (of course, you can always use various clever #include etc to keep one single textfile as the main source, and just include the same file in multiple places)
Of course, the source code of the compiler is available. I'm sure the maintainers of GCC and Clang/LLVM will happily take patches that improve on this. But bear in mind that the path from "parsing the source" to "emitting instructions" is quite long and complicated. What should happen if we do this:
#pragma use_sse=1
void func()
{
... some code goes here ...
}
#pragma use_sse=3
void func2()
{
...
func();
...
}
Now, func is short enough to be inlined, should the compiler inline it? If so, should it use sse1 or sse3 instructions for func().
I understand that YOU may not care about that sort of difficulty, but the maintainers of Clang and GCC will indeed have to deal with this in some way.
Edit:
In the headerfiles declaring the SSE intrinsics (and many other intrinsics), a typical function looks something like this:
extern __inline __m128 __attribute__((__gnu_inline__, __always_inline__, __artificial__))
_mm_add_ss (__m128 __A, __m128 __B)
{
return (__m128) __builtin_ia32_addss ((__v4sf)__A, (__v4sf)__B);
}
The builtin_ia32_addss is only available in the compiler when you have enabled the -msse option. So if you convince the compiler to still allow you to use the _mm_add_ss() when you have -mno-sse, it will give you an error for "__builtin_ia32_addss is not declared in this scope" (I just tried).
It would probably not be very hard to change this particular behaviour - there are probably only a few places where the code does the "introduce builtin functions". However, I'm not convinced that there are further issues in the code, later on when it comes to actually issuing instructions in the compiler.
I have done some work with "builtin functions" in a Clang-based compiler, and unfortunately, there are several steps involved in getting from the "parser" to the "code generation", where the builtin function gets involved.
Edit2:
Compared to GCC, solving this for Clang is even more complex, in that the compiler itself has understanding of SSE instructions, so it simply has this in the header file:
static __inline__ __m128 __attribute__((__always_inline__, __nodebug__))
_mm_add_ps(__m128 __a, __m128 __b)
{
return __a + __b;
}
The compiler will then know that to add a couple of __m128, it needs to produce the correct SSE instruction. I have just downloaded Clang (I'm at home, my work on Clang is at work, and not related to SSE at all, just builtin functions in general - and I haven't really done much of the changes to Clang as such, but it was enough to understand roughly how builtin functions work).
However, from your perspective, the fact that it's not a builtin function makes it worse, because the operator+ translation is much more complicated. I'm pretty sure the compiler just makes it into an "add these two things", and then pass it to LLVM for further work - LLVM will be the part that understands SSE instructions etc. But for your purposes, this makes it worse, because the fact that this is an "intrinsic function" is now pretty much lost, and the compiler just deals with it just as if you'd written a + b, with the side effect of a and b being types that are 128 bits long. It makes it even more complicated to deal with generating "the right instructions" and yet keeping "all other" instructions at a different SSE level.
I want to use C with templates on a embedded environment and I wanted to know what is the cost of compiling a C program with a C++ compiler?
I'm interested in knowing if there will be more code than the one the C compiler will generate.
Note that as the program is a C program, is expect to call the C++ compiler without exception and RTTI support.
Thanks,
Vicente
The C++ compiler may take longer to compile the code (since it has to build data structures for overload resolution, it can't know ahead of time that the program doesn't use overloads), but the resulting binary should be quite similar.
Actually, one important optimization difference is that C++ follows strict aliasing rules by default, while C requires the restrict keyword to enable aliasing optimizations. This isn't likely to affect code size much, but it could affect correctness and performance significantly.
There's probably no 'cost', assuming that the two compilers are of equivalent quality. The traditional objection to this is that C++ is much more complex and so it's more likely that a C++ compiler will have bugs in it.
Realistically, this is much less of a problem that it used to be, and I tend to do most of my embedded stuff now as a sort of horrible C/C++ hybrid - taking advantage of stronger typing and easier variable declaration rules, without incurring RTTI or exception handling overheads. If you're taking a given compiler (GCC, etc) and switching it from C to C++ mode, then much of what you have to worry about is common to the two languages anyway.
The only way to really know is for you to try it with the compilers you care about. A quick experiment here on a trivial program shows that the output is the same.
Your program will be linked to the C++ runtime library, not the C one. The C++ is larger as well.
Also, there are a couple of differences between C and C++ (aliases were already pointed out) so it may happen that your C code just does not compile in C++.
If it's C, then you can expect it will be exactly the same.
To elaborate: both C and C++ will forward their parse tree into the same backend that generates code (possibly via another intermediate representation), which means that if the code is functionally identical, the output will look the same (or nearly so).
Templates do "inflate" code, but you would otherwise have to write the same code or use macros to the same effect, so this is no "extra cost". Contrarily, the compiler may be able to optimize templates better in some cases.
A C++ compiler cannot compile C code. It can only compile C++, including a very ugly language which is the intersection of C and C++ and the worst of both worlds. Some C code will fail to compile at all on a C++ compiler, for example:
char *s = malloc(len+1);
While other C code will be compiled to the wrong thing, for example:
sizeof 'a'
I have found this extra-ordinary document Technical Report on C++ Performance. I have found there all the answers i was looking for.
Thanks to all that have answered this question.
There will be more code because that is what templates do. They are a stencil for generating (more) code.
Otherwise, you should see no differences between compiling a C program with a C compiler versus compiling with a C++ compiler.
If you don't use any of the extra "features" there should be no difference in size or behavior of the end result.
Although the C code will likely compile to something very similar (assuming there's no exception support enabled), using templates can very rapidly result in large binaries - you have to be careful, because every template instantiation can recursively result in other templates being implicitly instantiated as well.
There was a time when the C++ compiler linked in a bunch of C++ stuff even if the program didnt use it and you would see binaries that were 10 to 100 times larger than the C compiler would produce. I think a lot of that has gone away.
Since this is tagged "embedded", I assume its for embedded systems?
In that case, the major difference between C and C++ is the way C++ treats structs. All structs will be treated like classes, meaning they will have constructors.
All instances of structs/classes declared at file scope or as static will then have their constructors called before main() is executed, in a similar manner to static initialization, which you already have there no matter C or C++.
All these constructor calls at bootup is a major disadvantage in efficiency for embedded systems, where the code resides in NVM and not in RAM. Just like static initialization, it will create an ugly, undesired workload peak at the start of the program, where values from NVM are copied into the RAM.
There are ways around the static initialization in C/C++: most embedded compilers have an option to disable it. But since that is a non-standard setup, all code using statics would then have to be written so that it never uses any initialization values, but instead sets all static variables in runtime.
But as far as I know, there is no way around calling constructors, without violating the standard.
EDIT:
Here is source code executed in one such C++ system, Freescale HCS08 Codewarrior 6.3. This code is injected in the user program after static initialization, but before main() is executed:
static void Call_Constructors(void) {
int i;
...
i = (int)(_startupData.nofInitBodies - 1);
while (i >= 0) {
(&_startupData.initBodies->initFunc)[i](); /* call C++ constructors */
i--;
}
...
At the very least, this overhead code must be executed at program startup, no matter how efficient the compiler is at converting constructors into static initializtion.
C++ runtime start-up differs slightly from C start-up because it must invoke the constructors for global static objects before main() is called. This call loop is trivial and should not add much.
In the case of C++ code that is also entirely C compilable no static constructors will be present so the loop will not iterate.
In most cases apart from that, you will normally see no significant difference, in C++ you only pay for what you use.