Какой тип данных C11 является массивом в соответствии с AMD64 ABI

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

Я перешел к "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 с clang3.7, который ориентирован на Linux, использующий тот же ABI.


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

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

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

1 ответ

Решение

Голые массивы как аргументы функций в C и C++ всегда распадаются на указатели, как и в некоторых других контекстах.

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


Как указывает Кит Томсон, соответствующей частью стандарта C является раздел 7.7.6.3 пункта 7 N1570.

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

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


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

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

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

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

gcc6.1 компилирует его (для AMD64 SysV ABI, с -O3 ) к следующему:

    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

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

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

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

Чтение бонусов SysV ABI: Как указывает вики-тег x86, текущая версия стандарта ABI не полностью документирует поведение, на которое опираются компиляторы: знак clang/gcc / ноль расширяет узкие аргументы до 32 бит.


Обратите внимание, что для гарантии того, что функция arg является массивом фиксированного размера, C99 и более поздние версии позволяют использовать static Ключевое слово по-новому: по размерам массива. (Это все еще передается как указатель, конечно. Это не меняет ABI).

void bar(int arr[static 10]);

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

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

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


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

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

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

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

"Составной" - это общий термин, который включает в себя массивы, структуры и объединения.

Другие вопросы по тегам