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.