argc와 argv의 주소가 12 바이트 인 이유는 무엇입니까?


40

내 컴퓨터 (Linux를 실행하는 64 비트 Intel)에서 다음 프로그램을 실행했습니다.

#include <stdio.h>

void test(int argc, char **argv) {
    printf("[test] Argc Pointer: %p\n", &argc);
    printf("[test] Argv Pointer: %p\n", &argv);
}

int main(int argc, char **argv) {
    printf("Argc Pointer: %p\n", &argc);
    printf("Argv Pointer: %p\n", &argv);
    printf("Size of &argc: %lu\n", sizeof (&argc));
    printf("Size of &argv: %lu\n", sizeof (&argv));
    test(argc, argv);
    return 0;
}

프로그램의 출력은

$ gcc size.c -o size
$ ./size
Argc Pointer: 0x7fffd7000e4c
Argv Pointer: 0x7fffd7000e40
Size of &argc: 8
Size of &argv: 8
[test] Argc Pointer: 0x7fffd7000e2c
[test] Argv Pointer: 0x7fffd7000e20

포인터의 크기 &argv는 8 바이트입니다. 나는의 주소를 예상 argc할 수를 address of (argv) + sizeof (argv) = 0x7ffed1a4c9f0 + 0x8 = 0x7ffed1a4c9f8하지만, 4 바이트 패딩 그들 사이에 존재한다. 왜 이런 경우입니까?

내 생각 엔 메모리 정렬 때문일 수 있지만 확실하지 않습니다.

내가 호출하는 함수와 동일한 동작을 발견했습니다.


15
왜 안돼? 174 바이트 떨어져있을 수 있습니다. 대답은 운영 체제 및 / 또는 설정을 수행하는 래퍼 라이브러리에 따라 다릅니다 main.
— aschepler

2
@aschepler : 설정을 수행하는 래퍼에 의존해서는 안됩니다 main. C에서는 main일반 함수로 호출 될 수 있으므로 일반 함수와 같은 인수를 받아야하며 ABI를 준수해야합니다.
— Eric Postpischil

@ aschelper : 다른 기능에서도 동일한 동작을 발견했습니다.
— letmutx

4
흥미로운 '생각 실험'이지만 실제로는 '왜 궁금해'이상이어야 할 것은 없습니다. 이 주소는 운영 체제, 컴파일러, 컴파일러 버전, 프로세서 아키텍처에 따라 변경 될 수 있으며 '실제'에 의존해서는 안됩니다.
— 닐

2
sizeof의 결과는%zu
— phuclv

답변:


61

시스템에서 처음 몇 개의 정수 또는 포인터 인수는 레지스터로 전달되며 주소가 없습니다. &argc또는로 주소를 가져 오면 &argv컴파일러는 레지스터 내용을 스택 위치에 기록하고 해당 스택 위치의 주소를 제공하여 주소를 조작해야합니다. 그렇게 할 때 컴파일러는 어떤 점에서 편리한 스택 위치를 선택합니다.


6
스택에 전달 되더라도 이런 일이 발생할 수 있습니다 . 컴파일러는 값이 들어가는 로컬 객체의 저장소로 스택에서 들어오는 값 슬롯을 사용할 의무가 없습니다. 함수가 결국 테일 콜로 가고 테일 콜에 대한 나가는 인수를 생성하기 위해 이러한 객체의 현재 값이 필요하다는 것을 의미합니다.
— R .. GitHub 중지 지원 얼음

10

argc와 argv의 주소가 12 바이트 인 이유는 무엇입니까?

언어 표준의 관점에서 대답은 "특별한 이유가 없습니다"입니다. C는 함수 매개 변수의 주소 사이의 관계를 지정하거나 암시하지 않습니다. @EricPostpischil은 특정 구현에서 발생할 수있는 일을 설명하지만 모든 인수가 스택에 전달되는 구현에서는 세부 사항이 다를 수 있으며 이것이 유일한 대안은 아닙니다.

또한, 나는 그러한 정보가 프로그램 내에서 유용 할 수있는 방법을 고안하는 데 어려움을 겪고 있습니다. 예를 들어의 주소가의 주소보다 argv12 바이트 앞 이라는 것을 "알아도" argc그 포인터 중 하나를 다른 포인터로부터 계산하는 방법은 아직 정의되어 있지 않습니다.


7
@ R..GitHubSTOPHELPINGICE : 서로 컴퓨팅하는 것은 부분적으로 정의되어 있지만 잘 정의되어 있지 않습니다. C 표준은 변환 uintptr_t이 수행되는 방식에 엄격 하지 않으며, 매개 변수 주소 또는 인수가 전달되는 위치 간의 관계를 확실히 정의하지 않습니다.
— Eric Postpischil

6
@ R..GitHubSTOPHELPINGICE : 왕복 할 수 있다는 사실은 g (f (x)) = x를 의미합니다. 여기서 x는 포인터이고, f는 포인터를 uintptr_t로, g는 convert-uintptr_t-to를 의미합니다. -바늘. 수학적으로나 논리적으로 g (f (x) +4) = x + 4를 의미하지는 않습니다. 예를 들어, f (x)가 x²이고 g (y)가 sqrt (y) 인 경우 g (f (x)) = x (실제 음이 아닌 x의 경우)이지만 g (f (x) +4) ≠ x + 4 포인터의 경우로 변환 uintptr_t하면 상위 24 비트의 주소와 하위 8 비트의 일부 인증 비트가 제공 될 수 있습니다. 그런 다음 4를 추가하면 인증을 망칠 수 있습니다. 그것은 ... 업데이트하지 않습니다
— 에릭 Postpischil

5
… 주소 비트. 또는 uintptr_t 로의 변환은 상위 16 비트의 기본 주소와 하위 16 비트의 오프셋을 제공 할 수 있고 하위 비트에 4를 추가하면 상위 비트가 될 수 있지만 스케일링이 잘못되었습니다 (주소가 표시되지 않기 때문에) base • 65536 + offset이지만 일부 시스템에서와 마찬가지로 base • 64 + offset입니다. 간단히 말해서 uintptr_t전환에서 얻는 것이 반드시 간단한 주소는 아닙니다.
— 에릭 Postpischil

4
@ R..GitHubSTOPHELPINGICE 표준을 읽었을 때와 (void *)(uintptr_t)(void *)p비교할 수있는 약한 보증 만 (void *)p있습니다. 위원회는이 구현에 거의 정확한 문제에 대해 언급했다는 점을 주목할 필요가있다. "구현은 또한 서로 다른 기원에 기초한 포인터 는 비트가 동일하더라도 별개의 것으로 취급 할 수있다"는 결론을 내린다 .
— 라이언 아벨라

5
@ R..GitHubSTOPHELPINGICE : 죄송합니다. uintptr_t포인터가 다르거 나 바이트 단위의 "알려진"거리가 아니라 주소 변환이 다른 것으로 계산 된 값을 추가하고있는 것을 놓쳤습니다 . 물론, 그것은 사실이지만 어떻게 유용합니까? 그것은 응답 상태 "타방에서 그 포인터 중 하나를 계산하는 어떠한 정의 방법은 아직 거기에"사실 남아 있지만 그 계산은 계산하지 않습니다 b에서 a오히려 계산 b에서 모두 a하고 b있기 때문에, b양을 계산하기 위해 감산에 사용되어야한다 추가합니다. 서로 컴퓨팅하는 것은 정의되어 있지 않습니다.
— Eric Postpischil
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.