Is it possible to calculate function length at compile time in C++?

Viewed 407

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.

3 Answers

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