Place static constexpr *member* variables in specific linker sections on ARM GCC (e.g. ARM m4)

Viewed 208

In modern C++ (C++20) with GCC and/or Clang, is there a way to specify the linker section of static constexpr class member variables, e.g. as in

template<size_t N>
struct StringLiteral {
    char value[N];
};

template <StringLiteral S>
struct ConstexprStr {
    static constexpr const char[]
        value __attribute__((section("constexpr_strings")))
        = S::value;
};

// now use the address of that at runtime e.g. in a logging function as follows:
void pass_static_string_to_log_backend(const char *msg){
    // exposition only
    indicate_a_static_string_being_logged(
        reinterpret_cast<std::uintptr_t>(msg));
}

// Now we can define a log macro as follows
#define LOG(msg)                        \
   ::pass_static_string_to_log_backend( \
       ConstexprStr<msg>::value         \
   )

int main(){
   // using the above template trick, we "magically" created a 
   // global static constant from a local scope:
   LOG("hello world");
   return 0;
}

The attribute above seems to have no effect - the GCC documentation explicitly that it applies to global variables not local ones, but doesn't explicitly mention (constexpr) static member variables (and I never fully wrapped my head around the details of the C++ concept of linkage).

The background to the question is the following:

I'm experimenting with ways to optimise logging on small, memory-constrained microcontrollers. One can for example consider not to format any messages at log-time at all, just storing the addresses of the strings as well as the variables into some binary stream. An implementation of this idea is seen for example in the binary_log library.

In many cases, the log-output is never needed on the micro-controller at all, but only at a later point off-device. Now we wonder: Why store those strings in flash at all, wasting bytes that are never going to be accessed? We just need an arbitrary number to associate with each message. The challenge is to let the compiler/linker create these numbers, as well as a listing of the corresponding strings.

If I could collect those strings in a separate section, then I could move that section outside of the flash space using the linker and strip it before creating the final image. For message re-assembly I simply store the binary of that section elsewhere and read the strings from it.

Here is my full experiment on GodBolt.

An alternative approach would be to create a hash of the strings at compile time and inject the strings into the debug information somehow. But that feels much less elegant, and I have yet to find a good way to store the string in the debug info. Demangling StringLiteral<8ul>{char [8]{(char)119, (char)97, (char)114, (char)110, (char)32, (char)123, (char)125}} doesn't feel like a good way.

1 Answers

It is possible that the bug pointed at the comments is at play here - however there are I think two other issues.

The first is that the section attribute is put to template class members and not to concrete instantiations of data. Because of this the compiler doesn't know what data it should put into the section as the section attribute is not sticky. So if you take your code example and you do something like in the code below it will work as you expect for the Debug StringLiteral:

// All your code upto before your main function

static constexpr StringLiteral Debug __attribute__((section("SEB"))) = "debug {}";

int main() {
    int cnt=0;

    logger1.debug<Debug>(cnt++);

    // Your other code from main
}

In effect you have to take all your actual constexpr strings and apply the section to each one individually.

The second problem is trickier. When you compile your code using -O2, as you do now, even with my modification - the string will be completely optimized away. It will not appear in any section. Try the code with -O0 and you will see the SEB section in the output.

Changing the above declaration/Definition of Debug in the following way should work even with optimization (although it is not pretty).

extern const char S_Debug[] __attribute__((section("SEB"))) = "debug {}";;
static constexpr StringLiteral Debug = S_Debug;

Here is the modified example in GodBolt

Related