What's the difference between const array and static const array in C/C++
A local variable declared as static
has a lifetime of the entire running program, and is typically stored in the data segment. Compilers implement this by having a section that has the values in them.
Local variables not declared as static typically live on the stack and must be initialized every time the variable's scope is entered.
Looking at the assembly for the static
case, MSVC 2015 outputs the following:
; Listing generated by Microsoft (R) Optimizing Compiler Version 19.00.24215.1 TITLE MyLBP.c .686P .XMM include listing.inc .model flatINCLUDELIB LIBCMTINCLUDELIB OLDNAMESCONST SEGMENT?Arr@?1??tfuuuuuuu@@9@9 DQ 04060c00000000000r ; 134 ; `tfuuuuuuu'::`2'::Arr DQ 03fe15efd20a7955br ; 0.542845 DQ 03fdf59701e4b19afr ; 0.489834 DQ 0bfd8e38e9ab7fcb1r ; -0.388889 DQ 0bfe59f22c01e68a1r ; -0.675676 DQ 0bfeb13b15d5aa410r ; -0.846154 DQ 0bfe2c2355f07776er ; -0.586207 DQ 03fefffffbf935359r ; 1 ... ORG $+1036128CONST ENDSPUBLIC _tfuuuuuuuEXTRN __fltused:DWORD; Function compile flags: /Odtp_TEXT SEGMENT_Ind$ = 8 ; size = 4_tfuuuuuuu PROC; File c:\users\dennis bush\documents\x2.c; Line 4 push ebp mov ebp, esp; Line 106 mov eax, DWORD PTR _Ind$[ebp] fld QWORD PTR ?Arr@?1??tfuuuuuuu@@9@9[eax*8]; Line 107 pop ebp ret 0_tfuuuuuuu ENDP_TEXT ENDSEND
While gcc 4.8.5 outputs the following:
.file "MyLBP.c" .text .globl tfuuuuuuu .type tfuuuuuuu, @functiontfuuuuuuu:.LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movl -4(%rbp), %eax cltq movq Arr.1724(,%rax,8), %rax movq %rax, -16(%rbp) movsd -16(%rbp), %xmm0 popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc.LFE0: .size tfuuuuuuu, .-tfuuuuuuu .section .rodata .align 32 .type Arr.1724, @object .size Arr.1724, 1238400Arr.1724: .long 0 .long 1080082432 .long 547853659 .long 1071734525 .long 508238255 .long 1071602032 .long 2595749041 .long -1076305010 .long 3223218337 ... .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)" .section .note.GNU-stack,"",@progbits
So both define the data globally and reference that global array directly.
Now lets look at the non-static code. First for VSMC2015:
; Listing generated by Microsoft (R) Optimizing Compiler Version 19.00.24215.1 TITLE MyLBP.c .686P .XMM include listing.inc .model flatINCLUDELIB LIBCMTINCLUDELIB OLDNAMESPUBLIC _tfuuuuuuuPUBLIC __real@3e45798ee2308c3aPUBLIC __real@3f40e1cf9350aa3cPUBLIC __real@3f43b1f90beff84bPUBLIC __real@3f4c6220dc6e8066PUBLIC __real@3f4ea4c648794089PUBLIC __real@3f50023666188dc0PUBLIC __real@3f53957e56f300e9PUBLIC __real@3f55235d7d33b25fPUBLIC __real@3f5828f66e5bd33aPUBLIC __real@3f5c044284dfce31PUBLIC __real@3f5c87c05341c674...EXTRN @__security_check_cookie@4:PROCEXTRN __chkstk:PROCEXTRN _memset:PROCEXTRN ___security_cookie:DWORDEXTRN __fltused:DWORD; COMDAT __real@bff0000000000000CONST SEGMENT__real@bff0000000000000 DQ 0bff0000000000000r ; -1CONST ENDS; COMDAT __real@bfefffffdfc9a9adCONST SEGMENT__real@bfefffffdfc9a9ad DQ 0bfefffffdfc9a9adr ; -1CONST ENDS; COMDAT __real@bfefffffbf935359CONST SEGMENT__real@bfefffffbf935359 DQ 0bfefffffbf935359r ; -1CONST ENDS; COMDAT __real@bfefffff9f5cfd06CONST SEGMENT__real@bfefffff9f5cfd06 DQ 0bfefffff9f5cfd06r ; -1CONST ENDS; COMDAT __real@bfefffff7f26a6b3CONST SEGMENT__real@bfefffff7f26a6b3 DQ 0bfefffff7f26a6b3r ; -1CONST ENDS; COMDAT __real@bfefffff5ef05060CONST SEGMENT__real@bfefffff5ef05060 DQ 0bfefffff5ef05060r ; -1CONST ENDS...; Function compile flags: /Odtp_TEXT SEGMENT_Arr$ = -1238404 ; size = 1238400__$ArrayPad$ = -4 ; size = 4_Ind$ = 8 ; size = 4_tfuuuuuuu PROC; File c:\users\dennis bush\documents\x2.c; Line 4 push ebp mov ebp, esp mov eax, 1238404 ; 0012e584H call __chkstk mov eax, DWORD PTR ___security_cookie xor eax, ebp mov DWORD PTR __$ArrayPad$[ebp], eax; Line 5 movsd xmm0, QWORD PTR __real@4060c00000000000 movsd QWORD PTR _Arr$[ebp], xmm0 movsd xmm0, QWORD PTR __real@3fe15efd20a7955b movsd QWORD PTR _Arr$[ebp+8], xmm0 movsd xmm0, QWORD PTR __real@3fdf59701e4b19af movsd QWORD PTR _Arr$[ebp+16], xmm0 movsd xmm0, QWORD PTR __real@bfd8e38e9ab7fcb1 movsd QWORD PTR _Arr$[ebp+24], xmm0 movsd xmm0, QWORD PTR __real@bfe59f22c01e68a1 movsd QWORD PTR _Arr$[ebp+32], xmm0 movsd xmm0, QWORD PTR __real@bfeb13b15d5aa410 movsd QWORD PTR _Arr$[ebp+40], xmm0 movsd xmm0, QWORD PTR __real@bfe2c2355f07776e movsd QWORD PTR _Arr$[ebp+48], xmm0 ... push 1036128 ; 000fcf60H push 0 lea eax, DWORD PTR _Arr$[ebp+202272] push eax call _memset add esp, 12 ; 0000000cH; Line 106 mov ecx, DWORD PTR _Ind$[ebp] fld QWORD PTR _Arr$[ebp+ecx*8]; Line 107 mov ecx, DWORD PTR __$ArrayPad$[ebp] xor ecx, ebp call @__security_check_cookie@4 mov esp, ebp pop ebp ret 0_tfuuuuuuu ENDP_TEXT ENDSEND
The initializers are still stored globally. However, notice how each value is given a name internally and that 2 move instruction are generated for each value in the array. Creating those names and the explicit moves is why it takes so long to generate the code.
And now the gcc 4.8.5 version:
.file "MyLBP.c" .section .rodata .align 32.LC0: .long 0 .long 1080082432 .long 547853659 .long 1071734525 .long 508238255 .long 1071602032 .long 2595749041 .long -1076305010 .long 3223218337 .long -1075470558 ... .text .globl tfuuuuuuu .type tfuuuuuuu, @functiontfuuuuuuu:.LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $1238416, %rsp movl %edi, -1238404(%rbp) leaq -1238400(%rbp), %rax movl $.LC0, %ecx movl $1238400, %edx movq %rcx, %rsi movq %rax, %rdi call memcpy ; <-------------- call to memcpy movl -1238404(%rbp), %eax cltq movq -1238400(%rbp,%rax,8), %rax movq %rax, -1238416(%rbp) movsd -1238416(%rbp), %xmm0 leave .cfi_def_cfa 7, 8 ret .cfi_endproc.LFE0: .size tfuuuuuuu, .-tfuuuuuuu .ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)" .section .note.GNU-stack,"",@progbits
Rather than generating explicit instructions to copy each value, gcc just calls memcpy
to copy the values from global data into the local array, so generating the initialization code is much faster.
So the moral of the story is that MSVC is being very inefficient in how it initializes local variables.
Also, as noted in the comments, this is a confirmed bug which is due to be fixed in VS 2019.
const
or not, a non-static
function local must be constructed whenever the function is entered and the declaration is reached. Your compiler is spending time generating the code to perform that action at runtime, which may be arduous when the initialiser is super long.
By constrast, a static
of this form can just have its initial value plonked into the executable somewhere, with no runtime spin-up needed.
It does sound like a bit of a QoI issue with your compiler if you're really seeing a big difference in build times (particularly as 1.2MB isn't that much data), but the two pieces of code are fundamentally different and huge initialisers for things destined to live "on the stack" are typically something to avoid.