2016-08-06 2 views
2

OSX에서 사용되는 x86_64의 호출 규칙을 연구 중이며 the System V x86-64 ABI standard에서 "집계 및 공용체"섹션을 읽었습니다. 그것은 배열을 언급하고 나는 고정 된 길이의 C 배열과 같다고 생각했다. int[5].어떤 종류의 C11 데이터 유형이 AMD64 ABI에 따른 배열입니까

"3.2.3 매개 변수 전달"으로 이동하여 배열을 전달하는 방법에 대해 읽었고 정확히 이해하면 uint8_t[3]과 같은 것이 규칙 1에 지정된 4 개의 8 바이트 제한보다 작기 때문에 레지스터에 전달해야합니다 골재 유형 분류 (18 페이지 하단).

컴파일 한 후 대신 포인터로 전달되는 것을 볼 수 있습니다. (OSX 10.11.6의 Xcode 7.3.1에서 clang-703.0.31로 컴파일 중입니다.) 다음과 같이

내가 컴파일 사용 된 예제 소스는 다음과 같습니다

#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.

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에 리눅스를 대상으로 같은 ABI를 사용 : 나는 (otool -tV a.o을 실행하여) 생성 된 어셈블리를 분석하고 다음과 같은 출력을 얻을 수 otool 사용합니다.


누군가 C11의 데이터 형식이 배열에 적용될 수 있는지 궁금합니다. (clang은 C11을 사용하는 것으로 기본 설정되어 있습니다 (C99 인라인 기능 아래에 blurb here 참조).

또한 ARM standard에도 어레이 집계 유형이 지정되어 있어도 ARM과 비슷한 조사를 통해 유사한 결과가 나타났습니다.

또한 고정 길이 배열을 포인터로 처리하도록 지정된 일부 표준이 있습니까? 함수 C에서 인수 및 단지 여러 가지 다른 상황에서 같은 포인터로 C++ 항상 붕괴 등

+0

@PeterCordes : 배열은 모든 컨텍스트가 아닌 대부분의 포인터로 붕괴됩니다. 더 중요한 것은 C는 배열 타입의 매개 변수를 허용하지 않는다는 것입니다. void func (int param []);과 같은 선언에서,'param'의 타입은'int []'에서'int *'로 ** 조정 **됩니다. (이는 배열 표현식을 포인터로 암시 적으로 변환하도록 지정하는 규칙과 다릅니다.) –

+0

@PeterCordes 따라서 x86_64/arm 표준에 정의 된대로 배열로 간주되는 c11 또는 c99에는 데이터 유형이 없습니까? – DanZimm

+0

@KeithThompson C가 배열 형식의 매개 변수를 허용하지 않는다는 사실에 대한 참조가 있습니까? 나는 더 많이 읽고 싶다! – DanZimm

답변

7

베어 배열.

struct 또는 union 안에있는 배열은 값에 의해 전달되지 않습니다. 이것이 ABI가 베어드 어레이를 위해 C에서 발생하지는 않지만 전달 방법에 대해 신경을 써야하는 이유입니다.


Keith Thomson points out 같이, C 표준의 관련 부분은 N1570 section 6.7.6.3 paragraph 7

"형 배열"와 같은 변수의 선언하고, 여기서 "입력 정규화 포인터"를 조정되어야 타입 한정자 (있는 경우) [및] 어레이 형 도출 ... 내에 지정된

0 (물건 약 foo[static 10], 아래 참조)이다

다차원 배열은 배열 형식의 배열로 작동하므로 "배열 배열"의 가장 바깥 쪽 수준 만 배열 형식에 대한 포인터로 변환됩니다.


용어 : struct S 및 배열 "응집체"(연속적인 어드레스에 복수의 요소)가되는 곳의 의사 - 64 ABI는 ARM, 같은 용어를 사용한다. 따라서 "aggregates and unions"라는 문구가 많이 생깁니다. 왜냐하면 union은 언어와 ABI에 의해 비슷하게 처리되기 때문입니다.

ABI의 배열 통과 규칙을 적용하는 복합 유형 (struct/union/class)을 처리하는 재귀 규칙입니다. 이는 ASM을 볼 수있는 유일한 방법입니다 복사합니다 C에 대한 함수 인수의 일환으로 스택에 배열 또는 다음 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) :

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에서 숨겨진 포인터가 아닌 실제 복사 (레지스터 또는 스택으로의 복사)로 값을 전달합니다. 리턴 값에 의한 것은 리턴 값 rdx:rax의 128 비트 연결에 맞게 너무 커서 (및 벡터되지 않을 때, (rdi 단위) "숨겨진"첫번째 인수로 포인터를 전달 않는

참고 vector regs 등으로 반환)

ABI는 숨겨진 포인터를 사용하여 특정 크기보다 큰 값을 가진 객체를 사용할 수 있으며 호출 된 함수가 원본을 수정하지 않도록 신뢰할 수 있지만 그건 x86-64 ABI가하는 일이 아닙니다. 어떤 경우에는 더 좋을 것입니다 (특히 수정하지 않고 (즉 낭비) 많은 복사를하는 비효율적 인 C++의 경우). 그러나 다른 경우에는 더 나쁩니다.

시스템 V ABI 보너스 독서는 : clang/gcc sign/zero extend narrow args to 32bit 다음 태그 위키가 지적한 것처럼, ABI 표준의 현재 버전은 완전히 컴파일러에 의존 동작을 문서화하지 않습니다. 정말 함수 인수는 고정 된 크기의 배열을 보장하기 위해


주, 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++의 특징 중 하나 인 C99 가변 길이 배열과 C++에는없는 몇 가지 장점이 있습니다.

C++에서는 std::array<int,10>을 사용하여 컴파일러 크기 정보를 호출자에게 전달할 수 있습니다. 그러나 원하는 경우에만 수동으로 전달해야합니다. 이는 물론 int arr[10]을 포함하는 클래스이기 때문에 참조로 전달해야합니다. C 스타일 배열과 달리 자동으로 T*으로 감소하지 않습니다.


당신이 실제로 집계 유형의 배열을 호출하지 않는 것을 링크 된 ARM의 문서 : 4.3 절 복합 형 (정렬에 대해 설명하는) 그들에 표시에도 불구하고, 집계 유형의 배열을 구별 집합에 대한 정의의 특별한 경우이다.

복합 유형은 프로 시저 호출 레벨에서 단일 엔티티로 처리되는 하나 이상의 기본 데이터 유형의 모음입니다.

  • 멤버 메모리의 각 멤버는 A가 동일한 주소
  • 배열을 갖는다
  • 노조 순차적으로 배치되어 집계 : 복합 타입 중 임의의 것일 수있다 다른 유형 (그 기본 유형)의 반복 된 순서.

정의는 재귀 적입니다. 즉, 각 유형에 멤버로 복합 유형이 포함될 수 있습니다.

"복합체"는 배열, 구조체 및 공용체가 포함 된 포괄적 인 용어입니다.

+0

x86-84 ABI는 또한'array's aggregates도 호출합니다 :'aggregate (구조체와 배열)와 union type의 분류는 다음과 같이 작동합니다 :''array's가 아닌가? ABI)도 가치로 전달 될 수 있어야합니까? 추신 자세한 답변을 주셔서 감사합니다. 특히 정적 키워드의 조각 - 저는 결코 알지 못했습니다! – DanZimm

+0

@DanZimm : 쓰레기, 네 말이 맞아. 내가 실제로 C 배열 args에 무슨 일이 발생했는지에 대해 100 % 확실하다고 확신하기 때문에 용어를 두 번 확인하지 않았다. 포인터로 전달된다. 아마도 다른 언어가 배열의 가치에 의한 전달을 허용할까요? ABI 문서에는 포트란 섹션이 있습니다. –

+0

아마 Rust/Swift에서이 기능을 활용할 수 있습니까? 이 컴퓨터에 clang 소스가 없기 때문에 어디서나 값을 전달하는 배열이 기본 컴파일러에서 구현되는 것 같습니다. (용어가 잘못되었다고 생각하지만 배열을 올바르게 매핑하는 코드를 보았습니다. 포인터 대신 값으로 스택에 등록/등록). – DanZimm