user2635088 user2635088 - 29 days ago 13
C++ Question

C++ constexpr vs macro in string literal vs integer

I'm trying to figure out the difference "under the hood" between using a

constexpr
and a
preprocessor macro
to define integer and string literals.

#define FIRST_STRING "first_stringer"
constexpr char second_string[] = "second_stringer";

#define FIRST_INT 1234
constexpr int second_int = 12345;

int main ()
{
printf("%s\n", second_string);
printf("%s\n", FIRST_STRING);

printf("%d\n", FIRST_INT);
printf("%d\n", second_int);
return 0;
}

void hello() {
printf("%s\n", second_string);
printf("%s\n", FIRST_STRING);

printf("%d\n", FIRST_INT);
printf("%d\n", second_int);
}


which gives the following assembly output when compiled with
g++ -S main.cpp -std=c++11


.file "main.cpp"
.section .rodata
.LC0:
.string "first_stringer"
.LC1:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $_ZL13second_string, %edi
call puts
movl $.LC0, %edi
call puts
movl $1234, %esi
movl $.LC1, %edi
movl $0, %eax
call printf
movl $12345, %esi
movl $.LC1, %edi
movl $0, %eax
call printf
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.globl _Z5hellov
.type _Z5hellov, @function
_Z5hellov:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $_ZL13second_string, %edi
call puts
movl $.LC0, %edi
call puts
movl $1234, %esi
movl $.LC1, %edi
movl $0, %eax
call printf
movl $12345, %esi
movl $.LC1, %edi
movl $0, %eax
call printf
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size _Z5hellov, .-_Z5hellov
.section .rodata
.align 16
.type _ZL13second_string, @object
.size _ZL13second_string, 16
_ZL13second_string:
.string "second_stringer"
.align 4
.type _ZL10second_int, @object
.size _ZL10second_int, 4
_ZL10second_int:
.long 12345
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"
.section .note.GNU-stack,"",@progbits


On inspecting the assembly code, we can observe in both functions we have instructions
movl $1234, %esi
and
movl $12345, %esi
. I.e. there is no visible difference under the hood between a macro integer literal and a
constexp int
, even though the
constexpr int
is stored in a separate section
_ZL10second_int


On the other hand, for string literals, we see that the instruction
movl $_ZL13second_string, %edi
and
movl $.LC0, %edi
map their respective string literal to two different sections.

What is the difference between these two sections? Do they map to different parts of main memory once the executable is loaded? If yes, is one part faster to access than the other? I know I can profile the performance impact, but I'd like to understand the theoretical reason and difference between these two sections.

Answer

These are functionally equivalent. Note that the actual data in both cases is declared using the .string directive. The only difference is in the label names, where the one which is actually a C++ object (second_string) has a mangled name, whereas the macro just has a generic label name.

If you run objdump on the executable in Linux, you'll note that both strings are stored in the .rodata section:

String dump of section '.rodata':
  [     4]  %s^J
  [     8]  first_stringer
  [    17]  %d^J
  [    20]  second_stringer
Comments