Я немного экспериментирую со сборкой x86-64. Скомпилировав эту фиктивную функцию:
long myfunc(long a, long b, long c, long d,
long e, long f, long g, long h)
{
long xx = a * b * c * d * e * f * g * h;
long yy = a + b + c + d + e + f + g + h;
long zz = utilfunc(xx, yy, xx % yy);
return zz + 20;
}
С gcc -O0 -g
я был удивлен, обнаружив следующее в начале сборки функции:
0000000000400520 <myfunc>:
400520: 55 push rbp
400521: 48 89 e5 mov rbp,rsp
400524: 48 83 ec 50 sub rsp,0x50
400528: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi
40052c: 48 89 75 d0 mov QWORD PTR [rbp-0x30],rsi
400530: 48 89 55 c8 mov QWORD PTR [rbp-0x38],rdx
400534: 48 89 4d c0 mov QWORD PTR [rbp-0x40],rcx
400538: 4c 89 45 b8 mov QWORD PTR [rbp-0x48],r8
40053c: 4c 89 4d b0 mov QWORD PTR [rbp-0x50],r9
400540: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28]
400544: 48 0f af 45 d0 imul rax,QWORD PTR [rbp-0x30]
400549: 48 0f af 45 c8 imul rax,QWORD PTR [rbp-0x38]
40054e: 48 0f af 45 c0 imul rax,QWORD PTR [rbp-0x40]
400553: 48 0f af 45 b8 imul rax,QWORD PTR [rbp-0x48]
400558: 48 0f af 45 b0 imul rax,QWORD PTR [rbp-0x50]
40055d: 48 0f af 45 10 imul rax,QWORD PTR [rbp+0x10]
400562: 48 0f af 45 18 imul rax,QWORD PTR [rbp+0x18]
gcc
очень странным образом переносит все регистры аргументов в стек, а затем берет их из памяти для дальнейших операций.
Это происходит только на -O0
(с -O1
проблем нет), но все же почему? Мне это кажется антиоптимизацией - зачем gcc
это делать?
2 ответа
Я ни в коем случае не эксперт по внутреннему устройству GCC, но я попробую. К сожалению, большая часть информации о распределении и разливе регистров GCC кажется устаревшей (имеется ссылка на файлы, такие как local-alloc.c
, которые больше не существуют).
Я смотрю исходный код gcc-4.5-20110825
.
В GNU C Compiler Internals упоминается, что исходный код функции генерируется expand_function_start
в gcc/function.c
. Там мы находим следующее для обработки параметров:
4462 /* Initialize rtx for parameters and local variables.
4463 In some cases this requires emitting insns. */
4464 assign_parms (subr);
В assign_parms
код, который обрабатывает, где хранятся все аргументы, следующий:
3207 if (assign_parm_setup_block_p (&data))
3208 assign_parm_setup_block (&all, parm, &data);
3209 else if (data.passed_pointer || use_register_for_decl (parm))
3210 assign_parm_setup_reg (&all, parm, &data);
3211 else
3212 assign_parm_setup_stack (&all, parm, &data);
assign_parm_setup_block_p
обрабатывает агрегированные типы данных и неприменим в этом случае, и поскольку данные не передаются как указатель, GCC проверяет use_register_for_decl
.
Здесь соответствующая часть:
1972 if (optimize)
1973 return true;
1974
1975 if (!DECL_REGISTER (decl))
1976 return false;
DECL_REGISTER
проверяет, была ли переменная объявлена с ключевым словом register
. И теперь у нас есть ответ: большинство параметров находятся в стеке, когда оптимизации не включены, а затем обрабатываются assign_parm_setup_stack
. Маршрут, пройденный через исходный код до того, как он закончится утечкой значения, немного сложнее для аргументов указателя, но может быть прослежен в том же файле, если вам интересно.
Почему GCC выводит все аргументы и локальные переменные с отключенной оптимизацией? Чтобы помочь отладке. Рассмотрим эту простую функцию:
1 extern int bar(int);
2 int foo(int a) {
3 int b = bar(a | 1);
4 b += 42;
5 return b;
6 }
Скомпилировано с помощью gcc -O1 -c
на моей машине:
0: 48 83 ec 08 sub $0x8,%rsp
4: 83 cf 01 or $0x1,%edi
7: e8 00 00 00 00 callq c <foo+0xc>
c: 83 c0 2a add $0x2a,%eax
f: 48 83 c4 08 add $0x8,%rsp
13: c3 retq
Это нормально, за исключением случаев, когда вы прерываете строку 5 и пытаетесь распечатать значение a, вы получите
(gdb) print a
$1 = <value optimized out>
Поскольку аргумент перезаписывается, поскольку он не используется после вызова bar
.
Пара причин:
- В общем случае аргумент функции должен рассматриваться как локальная переменная, потому что он может быть сохранен или иметь адрес, взятый внутри функции. Поэтому проще всего выделить слот стека для каждого аргумента.
- Информацию об отладке становится намного проще выдавать с помощью местоположений стека: значение аргумента всегда находится в каком-то определенном месте, а не перемещается между регистрами и памятью.
Когда вы смотрите на код -O0 в целом, учитывайте, что главными приоритетами компилятора являются максимально возможное сокращение времени компиляции и создание высококачественной отладочной информации.
Похожие вопросы
Связанные вопросы
Новые вопросы
c
C - это язык программирования общего назначения, используемый для системного программирования (ОС и встраиваемых), библиотек, игр и кроссплатформенности. Этот тег следует использовать с общими вопросами, касающимися языка C, как это определено в стандарте ISO 9899 (последняя версия 9899: 2018, если не указано иное, а также для запросов, специфичных для версии, с c89, c99, c11 и т. Д.). C отличается от C ++ и не должен сочетаться с тэгом C ++ без разумной причины.