2016-08-06 7 views
2

Я изучал соглашение о вызове x86_64, которое используется в OSX, и читал раздел «Агрегаты и союзы» в the System V x86-64 ABI standard). Он упоминает массивы, и я решил, что это похоже на массив с фиксированной длиной c, например. int[5].Какой тип данных C11 представляет собой массив в соответствии с AMD64 ABI

Я перешел к «3.2.3 Прохождение параметров», чтобы прочитать о том, как массивы были переданы, и если я правильно понимаю, что-то вроде uint8_t[3] должно быть передано в регистрах, поскольку оно меньше, чем предел в четыре восемьбайт, введенный правилом 1 классификации агрегатных типов (стр. 18 у основания).

После компиляции я вижу, что вместо этого он передается как указатель. (Я компилирую с clang-703.0.31 из Xcode 7.3.1 в OSX 10.11.6).

Пример источника я использую для компиляции выглядит следующим образом:

#include <stdio.h> 

#define type char 

extern void doit(const type[3]); 
extern void doitt(const type[5]); 
extern void doittt(const type[16]); 
extern void doitttt(const type[32]); 
extern void doittttt(const type[40]); 

int main(int argc, const char *argv[]) { 
    const char a[3] = { 1, 2, 3 }; 
    const char b[5] = { 1, 2, 3, 4, 5 }; 
    const char c[16] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1 }; 
    const char d[32] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1 }; 
    const char e[40] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; 

    doit(a); 
    doitt(b); 
    doittt(c); 
    doitttt(d); 
    doittttt(e); 
} 

Я свалка, что в файле с именем a.c и использовать следующую команду для компиляции: clang -c a.c -o a.o. Я использую otool анализировать сборку генерируемый (запустив otool -tV a.o) и получить следующий вывод:

a.o: 
(__TEXT,__text) section 
_main: 
0000000000000000 pushq %rbp 
0000000000000001 movq %rsp, %rbp 
0000000000000004 subq $0x10, %rsp 
0000000000000008 leaq _main.a(%rip), %rax 
000000000000000f movl %edi, -0x4(%rbp) 
0000000000000012 movq %rsi, -0x10(%rbp) 
0000000000000016 movq %rax, %rdi 
0000000000000019 callq _doit 
000000000000001e leaq _main.b(%rip), %rdi 
0000000000000025 callq _doitt 
000000000000002a leaq _main.c(%rip), %rdi 
0000000000000031 callq _doittt 
0000000000000036 leaq _main.d(%rip), %rdi 
000000000000003d callq _doitttt 
0000000000000042 leaq _main.e(%rip), %rdi 
0000000000000049 callq _doittttt 
000000000000004e xorl %eax, %eax 
0000000000000050 addq $0x10, %rsp 
0000000000000054 popq %rbp 
0000000000000055 retq 

Или то же самое, здесь он находится на Godbolt compiler explorer with clang3.7, которая ориентирована на Linux, которая использует тот же ABI.


Итак, мне было интересно, может ли кто-нибудь привести меня к тем, какие типы данных в C11 относятся к массивам. (Похоже, что clang по умолчанию использует C11 - см. Расписание here прямо под встроенной функцией C99).

Я также провел аналогичное исследование с ARM и нашел аналогичные результаты, хотя ARM standard также указывает, что существует массив агрегатного типа.

Кроме того, существует ли где-то в каком-то стандарте, что указано, что массив с фиксированной длиной должен рассматриваться как указатель?

+0

@PeterCordes: Массивы распадаются на указатели в большинстве, но не во всех контекстах. Более того, C не разрешает параметры типа массива. В объявлении типа 'void func (int param []);', тип 'param' ** корректируется ** из' int [] 'в' int * '. (Это отличное правило от того, которое указывает неявное преобразование выражений массива в указатели.) –

+0

@PeterCordes, так нет ли типа данных в c11 или c99, который считается массивом, как определено в стандартах x86_64/arm? – DanZimm

+0

@KeithThompson У вас есть ссылка на то, что C не разрешает параметры типа массива? Я бы хотел больше узнать! – DanZimm

ответ

7

Bare массивы в качестве функциональных аргументов в C и C++ всегда распада на указатели, так же, как и в некоторых других контекстах.

Массивы внутри struct s или union s не передаются и передаются по значению. Вот почему ABI должны заботиться о том, как они передаются, хотя это не происходит в C для голых массивов.


В Keith Thomson points out, соответствующая часть стандарта C является N1570 section 6.7.6.3 paragraph 7

Декларация параметра как «массив типа» должен быть настроен на «квалифицированного указателя к типу», где классификаторы типа (если таковые имеются) те, которые указаны в [и] от типа массива вывода ... (материал о foo[static 10] см ниже)

Обратите внимание, что многомерные массивы работают как массивы типа массива, поэтому только внешний уровень «array-ness» преобразуется в указатель на тип массива.


Терминология: x86-64 ABI док использует ту же терминологию, как ARM, где struct ы и массивы «агрегаты» (несколько элементов в последовательных адресах). Таким образом, фраза «агрегаты и союзы» появляется много, потому что union s обрабатываются аналогично языком и ABI.

Это рекурсивное правило для обработки составных типов (struct/union/class), которое приводит в действие правила передачи массивов в ABI. Это единственный способ, вы увидите, ассемблер, что копирует массив в стек как часть функции ARG, для C или

struct s { int a[8]; }; 
void ext(struct s byval); 

void foo() { struct s tmp = {{0}}; ext(tmp); } 

gcc6.1 compiles it (for the AMD64 SysV ABI, with -O3) C++ к следующему:

sub  rsp, 40 # align the stack and leave room for `tmp` even though it's never stored? 
    push 0 
    push 0 
    push 0 
    push 0 
    call ext 
    add  rsp, 72 
    ret 

В ABI x86-64 значение pass-by-value происходит путем фактического копирования (в регистры или стек), а не с помощью скрытых указателей.

Обратите внимание, что возвращать по значению действительно проходит указатель, как «скрытый» первый арг (в rdi), когда возвращаемое значение является слишком большим, чтобы поместиться в 128bit конкатенациях rdx:rax (а не вектор существо возвращены в векторных регах и т. д.)

Возможно, ABI будет использовать скрытый указатель на объекты с передачей по значению выше определенного размера и доверять вызываемой функции не изменять оригинал, но это не то, что выбирает x86-64 ABI. Это было бы лучше в некоторых случаях (особенно для неэффективного C++ с большим количеством копирования без изменений (т. Е. Впустую)), но хуже в других случаях.

SysV ABI бонус чтения: Как тег вики указывает, что текущая версия стандарта ABI не полностью документировать поведение, компиляторы полагаются на: clang/gcc sign/zero extend narrow args to 32bit.


Обратите внимание, что на самом деле гарантировать, что функция Arg является массив фиксированного размера, C99 and later lets you use the static keyword in a new way: по размерам массива. (Это все равно передается как указатель, это не изменяет ABI).

void bar(int arr[static 10]); 

Это позволяет sizeof(arr) работу как можно было бы ожидать в вызываемой функции, а также позволяет предупреждения компилятора о выходе за пределы. Это также потенциально позволяет улучшить оптимизацию, если компилятор знает, что ему разрешено обращаться к элементам, которые нет в источнике C. (См. this blog post).

The same keyword page for C++ указывает, что ISO C++ не поддерживает это использование static; это еще одна из тех функций C-only, наряду с массивами переменной длины C99 и несколькими другими лакомствами, которых нет у C++.

В C++ вы можете использовать std::array<int,10>, чтобы получить информацию о размере времени компиляции, переданную вызывающему абоненту. Однако вам нужно вручную передать его по ссылке, если это то, что вы хотите, так как это, конечно, просто класс, содержащий int arr[10]. В отличие от массива в стиле C, он не распадается на T* автоматически.


тонарма документ, который вы связаны не кажется, на самом деле назвать массивы агрегат типа: Раздел 4.3 Составные типы (что обсуждается выравнивание) отличает массивы от агрегатных типов, даже если они кажутся быть частным случаем его определения для агрегатов.

Составной тип представляет собой набор типов один или более фундаментальных данных, которые обрабатываются как единое целое на уровне вызова процедуры. Составной тип может быть любым из:

  • агрегатной, где члены изложены последовательно в памяти
  • А соединение, где каждый из членов имеет один и тот же адрес
  • массив, который представляет собой повторяющаяся последовательность другого типа (его базовый тип).

Определения являются рекурсивными; то есть каждый из типов может содержать композитный тип в качестве элемента

«Композитный» является зонтичным термином, который включает в себя массивы, структуры и союзы.

+0

Документ AB86 x86-84 также вызывает «агрегаты массива»: «Классификация агрегатов (структур и массивов) и типов объединения работает следующим образом:' так что не должно быть так, что 'array' (как определить по ABI) также должны быть переданы по значению? Постскриптум Спасибо за подробный ответ, особенно на статическое ключевое слово - я этого никогда не знал! – DanZimm

+0

@DanZimm: дерьмо, ты прав. Я не дважды проверял терминологию, так как был (и до сих пор) на 100% уверен, что я прав о том, что на самом деле происходит с аргументами массива C: они передаются как указатели. Возможно, другие языки допускают пропускную способность массивов? В документе ABI есть раздел Fortran. –

+0

Возможно, Rust/Swift может воспользоваться этим? У меня нет источника clang на этом компьютере, поэтому я не помню, где, но передача массивов по значению, кажется, реализована в базовом компиляторе (я думаю, что моя терминология неверна, но я видел код, который правильно отображает массив на register/в стек по значению вместо указателя). – DanZimm