В чем разница между константным массивом и статическим константным массивом в C/С++

Компиляция следующего кода в Visual Studio 2015 (Win7, x64, настройка отладки) заняла очень, очень, очень много времени (т.е. Более 10 минут)

double tfuuuuuuu(int Ind)
{
  const double Arr[600 * 258] = {3.5453, 45.234234234, 234234.234,// extends to 258 values for each line
                                // 599 lines here.....
                                };                     
  return Arr[Ind];
}

Но когда я добавил ключевое слово static, компиляция заняла полсекунды

double tfuuuuuuu(int Ind)
{
  static const double Arr[600 * 258] = {3.5453, 45.234234234, 234234.234,// extends to 258 values for each line
                                // 599 lines here.....
                                };                     
  return Arr[Ind];
}

Я знаю, что static означает, что переменная будет сохранять свое значение между вызовами, но если массив в любом случае является const какая разница, если я добавлю static? и почему время компиляции так резко изменилось?

РЕДАКТИРОВАТЬ:

Фактический код можно найти здесь (компиляция была в режиме отладки)

Ответы

Ответ 1

const или нет, static функция non- local должна создаваться всякий раз, когда функция вводится и достигается объявление. Ваш компилятор тратит время на генерацию кода для выполнения этого действия во время выполнения, что может быть трудным, когда инициализатор слишком длинный.

С другой стороны, static этой формы может просто иметь свое начальное значение где-то в исполняемом файле, без необходимости ускорения выполнения.

Это звучит как проблема QoI с вашим компилятором, если вы действительно видите большую разницу во времени сборки (особенно, если 1,2 МБ не так уж много данных), но эти две части кода принципиально различаются и имеют огромные инициализаторы поскольку вещи, предназначенные для жизни "в стеке", как правило, являются чем-то, чего следует избегать.

Ответ 2

Локальная переменная, объявленная как static имеет время жизни всей работающей программы и обычно хранится в сегменте данных. Компиляторы реализуют это, имея раздел, в котором есть значения.

Локальные переменные, не объявленные как статические, обычно находятся в стеке и должны инициализироваться каждый раз при вводе области видимости переменной.

Рассматривая сборку static корпуса, MSVC 2015 выдает следующее:

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.00.24215.1 

    TITLE   MyLBP.c
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

CONST   SEGMENT
[email protected][email protected]@[email protected] 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 $+1036128
CONST   ENDS
PUBLIC  _tfuuuuuuu
EXTRN   __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 [email protected][email protected]@[email protected][eax*8]
; Line 107
    pop ebp
    ret 0
_tfuuuuuuu ENDP
_TEXT   ENDS
END

В то время как gcc 4.8.5 выдает следующее:

    .file   "MyLBP.c"
    .text
    .globl  tfuuuuuuu
    .type   tfuuuuuuu, @function
tfuuuuuuu:
.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, 1238400
Arr.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

Таким образом, оба определяют данные глобально и напрямую ссылаются на этот глобальный массив.

Теперь давайте посмотрим на нестатический код. Первый для VSMC2015:

; Listing generated by Microsoft (R) Optimizing Compiler Version 19.00.24215.1 

    TITLE   MyLBP.c
    .686P
    .XMM
    include listing.inc
    .model  flat

INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES

PUBLIC  _tfuuuuuuu
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
PUBLIC  [email protected]
...
EXTRN   @[email protected]:PROC
EXTRN   __chkstk:PROC
EXTRN   _memset:PROC
EXTRN   ___security_cookie:DWORD
EXTRN   __fltused:DWORD
;   COMDAT [email protected]
CONST   SEGMENT
[email protected] DQ 0bff0000000000000r   ; -1
CONST   ENDS
;   COMDAT [email protected]
CONST   SEGMENT
[email protected] DQ 0bfefffffdfc9a9adr   ; -1
CONST   ENDS
;   COMDAT [email protected]
CONST   SEGMENT
[email protected] DQ 0bfefffffbf935359r   ; -1
CONST   ENDS
;   COMDAT [email protected]
CONST   SEGMENT
[email protected] DQ 0bfefffff9f5cfd06r   ; -1
CONST   ENDS
;   COMDAT [email protected]
CONST   SEGMENT
[email protected] DQ 0bfefffff7f26a6b3r   ; -1
CONST   ENDS
;   COMDAT [email protected]
CONST   SEGMENT
[email protected] DQ 0bfefffff5ef05060r   ; -1
CONST   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 [email protected]
    movsd   QWORD PTR _Arr$[ebp], xmm0
    movsd   xmm0, QWORD PTR [email protected]
    movsd   QWORD PTR _Arr$[ebp+8], xmm0
    movsd   xmm0, QWORD PTR [email protected]
    movsd   QWORD PTR _Arr$[ebp+16], xmm0
    movsd   xmm0, QWORD PTR [email protected]
    movsd   QWORD PTR _Arr$[ebp+24], xmm0
    movsd   xmm0, QWORD PTR [email protected]
    movsd   QWORD PTR _Arr$[ebp+32], xmm0
    movsd   xmm0, QWORD PTR [email protected]
    movsd   QWORD PTR _Arr$[ebp+40], xmm0
    movsd   xmm0, QWORD PTR [email protected]
    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    @[email protected]
    mov esp, ebp
    pop ebp
    ret 0
_tfuuuuuuu ENDP
_TEXT   ENDS
END

Инициализаторы все еще хранятся глобально. Однако обратите внимание, как каждому значению присваивается имя внутри, и что для каждого значения в массиве создаются две инструкции перемещения. Создание этих имен и явных ходов - вот почему так долго генерируется код.

А теперь версия gcc 4.8.5:

    .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, @function
tfuuuuuuu:
.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

Вместо того, чтобы генерировать явные инструкции для копирования каждого значения, gcc просто вызывает memcpy для копирования значений из глобальных данных в локальный массив, поэтому генерация кода инициализации происходит намного быстрее.

Итак, мораль этой истории в том, что MSVC очень неэффективен в том, как инициализирует локальные переменные.

Также, как отмечено в комментариях, это подтвержденная ошибка, которая должна быть исправлена в VS 2019.

Ответ 3

Одним из основных различий между static constant и non static constant является связь. Любой массив или переменная, объявленная как static что означает static сообщает компилятору, что эта переменная или массив используется только внутри файла или функции, и компилятор будет рассматривать его как внутреннюю связь. Это также причина того, что программа компилируется быстро.