C / C ++ main argv가 왜“char * argv”가 아닌“char * argv []”로 선언됩니까?


21

argv"배열의 첫 번째 색인에 대한 포인터"( char* argv) 가 아니라 "배열의 첫 번째 색인에 대한 포인터"로 선언 됩니까?

여기서 "포인터에 포인터"라는 개념이 필요한 이유는 무엇입니까?


4
"배열의 첫 번째 인덱스를 가리키는 포인터"- char* argv[]또는에 대한 올바른 설명이 아닙니다 char**. 그것은 캐릭터를 가리키는 포인터입니다. 특히 외부 포인터는 배열의 첫 번째 포인터를 가리키고 내부 포인터는 널 종료 문자열의 첫 문자를 가리 킵니다. 여기에 관련된 인덱스가 없습니다.
Sebastian Redl

12
char * argv 인 경우 두 번째 인수를 어떻게 얻습니까?
gnasher729

15
공간을 올바른 장소에 놓으면 인생이 더 쉬워 질 것입니다. char* argv[]공간을 잘못된 곳에 넣습니다. 말 char *argv[], 지금은이 수단 "표현이 분명하다 *argv[n]유형의 변수입니다 char." 포인터와 포인터에 대한 포인터 등을 해결하려고 시도하지 마십시오. 선언은이 작업에 대해 수행 할 수 있는 작업 을 알려줍니다 .
Eric Lippert

1
char * argv[]비슷한 C ++ construct와 정신적으로 비교 std::string argv[]하면 구문 분석이 더 쉬울 수 있습니다. ... 그냥 실제로 시작하지 않는 작성 그런 식으로!
저스틴 타임 2 복원 모니카

2
@EricLippert는 질문에 C ++도 포함되어 있으며, 예 char &func(int);를 들어 &func(5)type을 만들지 않는 것을 가질 수 있습니다 char.
Ruslan

답변:


59

Argv는 기본적으로 다음과 같습니다.

여기에 이미지 설명을 입력하십시오

왼쪽에는 인수 자체가 있습니다. 실제로 main에 대한 인수로 전달됩니다. 여기에는 포인터 배열의 주소가 포함됩니다. 이들 각각은 명령 행에 전달 된 해당 인수의 텍스트를 포함하는 메모리의 특정 위치를 가리 킵니다. 그런 다음 해당 배열의 끝에 널 포인터가 보장됩니다.

개별 인수에 대한 실제 스토리지는 적어도 서로 개별적으로 할당 될 수 있으므로 메모리의 주소가 상당히 무작위로 배열 될 수 있습니다 (그러나 쓰기 방식에 따라 단일 연속 블록에있을 수도 있음) 기억-당신은 단순히 알지 못하고 신경 쓰지 않아야합니다).


52
어떤 레이아웃 엔진이 당신을 위해 그 도표를 그렸습니까?
Eric Lippert

43
@EricLippert 포인트가 인접하거나 순서가 맞지 않을 수 있음을 강조하기 위해 의도적으로 사용될 수 있습니다.
jamesdlin

3
의도적 인 것입니다
Michael

24
에릭이 아마 그렇게 생각했을 것 같지만 (올바로 IMO)는 어쨌든 코멘트가 재미 있다고 생각했습니다.
Jerry 관

2
@JerryCoffin은 실제 인수가 메모리에 인접하더라도 임의의 길이를 가질 수 argv[i]있으므로 이전의 모든 인수 를 스캔하지 않고도 액세스 할 수있는 각 포인터에 대해 별도의 포인터가 필요하다는 것을 지적 할 수도 있습니다.
ilkkachu

22

그것이 운영 체제가 제공하는 것이므로 :-)

귀하의 질문은 약간의 닭고기 / 계란 반전 문제입니다. 문제는 C ++에서 원하는 것을 선택하는 것이 아니라, C ++에서 OS가 제공하는 것을 말하는 방법입니다.

유닉스는 "문자열"배열을 전달하는데, 각 문자열은 명령 인수입니다. C / C ++에서 문자열은 "char *"이므로, 문자열 배열은 맛에 따라 char * argv [] 또는 char ** argv입니다.


13
아니요, 정확히 "C ++에서 원하는 것을 선택하는 문제"입니다. 예를 들어, Windows는 명령 행을 단일 문자열로 제공하지만 C / C ++ 프로그램은 여전히 argv배열을 수신 합니다. 런타임은 명령 행을 토큰 화 argv하고 시작시 배열을 관리합니다.
Joker_vD

14
나는 그것이 트위스트 방식으로 생각 @Joker_vD 입니다 OS가 당신을주는 것에 대해. 구체적으로 : C ++은 C가 이런 식으로했기 때문에 C ++이 그렇게했다고 생각합니다.
다니엘 바그너

1
@DanielWagner : 예, 이것은 C의 유닉스 유산입니다. 유닉스 / 리눅스에서는 최소한의 _start호출로 기존 포인터를 main전달 main하면됩니다argv 메모리 배열에 됩니다. 이미 올바른 형식입니다. 커널은 argv 인수 execve(const char *filename, char *const argv[], char *const envp[])에서 새 실행 파일을 시작 하기 위해 수행 된 시스템 호출로 커널을 복사합니다 . (Linux에서는 argv [] (배열 자체)와 argc가 프로세스 항목의 스택에 있습니다. 대부분의 Unix는 동일하다고 가정합니다.
Peter Cordes

8
그러나 Joker의 요점은 C / C ++ 표준이 args가 나온 구현에 맡겨야한다는 것입니다. 그들은 OS에서 똑바로 될 필요가 없습니다. 플랫 문자열을 전달 운영체제, A의 좋은argc=2 에서는 전체 플랫 문자열 을 설정 하고 전달하는 대신 토큰 화를 포함 . (표준의 편지를 따라하는 것으로는 충분하지 않습니다 유용 , 그것은 의도적으로 구현시 선택의 여지를 남긴다.) 일부 Windows 프로그램은 특별히 진짜 구현 평면 문자열을 얻을 수있는 방법을 제공 할 치료 지수를 원할 것입니다 만, 너무.
Peter Cordes

1
바 실레의 답변 은이 + @Joker의 수정 사항과 내 의견과 거의 비슷합니다.
Peter Cordes

15

먼저 매개 변수 선언 char **argv으로서 char *argv[]; 둘 다 문자열에 대한 포인터 (배열 또는 하나 이상의 가능한 포인터 집합)를 암시합니다.

다음으로, "pointer to char"만있는 경우 – 예를 들어 char *– n 번째 항목에 액세스하려면 n 번째 항목의 시작 부분을 찾기 위해 첫 번째 n-1 항목을 스캔해야합니다. (또한 이것은 각 문자열이 연속적으로 저장되어야한다는 요구 사항을 부과합니다.)

포인터 배열을 사용하면 n 번째 항목을 직접 색인화 할 수 있으므로 (문자열이 연속적이라고 가정하면 엄격하게 필요하지는 않지만) 일반적으로 훨씬 편리합니다.

설명하기 위해 :

./program hello world

argc = 3
argv[0] --> "./program\0"
argv[1] --> "hello\0"
argv[2] --> "world\0"

OS에서 제공되는 문자 배열은 다음과 같습니다.

            "./program\0hello\0world\0"
argv[0]      ^
argv[1]                 ^
argv[2]                        ^

argv가 단지 "char에 대한 포인터"인 경우

       "./program\0hello\0world\0"
argv    ^

그러나 (os의 디자인에 의해), 세 개의 문자열 "./program", "hello"및 "world"가 연속적이라는 보장은 없습니다. 또한, 이러한 종류의 "여러 연속 문자열에 대한 단일 포인터"는 특히 문자열에 대한 포인터 배열과 비교할 때 (C의 경우)보다 특이한 데이터 유형 구조입니다.


어떤 대신, 경우에 argv --> "hello\0world\0"당신이 argv --> index 0 of the array(안녕하세요), 그냥 일반 배열있다. 왜 이것이 불가능합니까? 그런 다음 배열 argc시간 을 계속 읽 습니다. 그런 다음 argv에 대한 포인터가 아닌 argv 자체를 전달합니다.
사용자

@auser, 그것이 argv-> "./program\0hello\0\world\0"입니다. 첫 번째 문자에 대한 포인터 (예 : ".") 첫 번째 \ 0을 지나서 포인터를 가져 가면 "hello \ 0"에 대한 포인터를 갖고 "world \ 0"에 대한 포인터를 갖습니다. argc 시간 (\ 0을 치는) 후에, 당신은 끝났다. 물론, 그것이 작동하도록 만들 수 있고, 내가 말했듯이, 특이한 구조
Erik Eidt

귀하의 예 argv[4]에서NULL
Basile Starynkevitch

3
(최소한 초기에) 보장합니다 argv[argc] == NULL. 이 경우에는 argv[3]그렇지 않습니다 argv[4].
Miral

1
@Hill, 그렇습니다. 널 문자 종결 자에 대해 명시 시키려고 노력했기 때문에 감사합니다.
Erik Eidt

13

C / C ++ 기본 argv가 "char * argv []"로 선언되는 이유

가능한 대답은 C11 표준 n1570 ( §5.1.2.2.1 프로그램 시작시 ) 및 C ++ 11 표준 n3337 ( §3.6.1 기본 기능 ) 이 호스트 환경에 필요 하지만 C 표준이 언급되어 있음을 주목하기 때문입니다. 또한 §5.1.2.1 환경 자립 ) 참조 .

다음 질문은 왜 C와 C ++ 표준 main이 그러한 int main(int argc, char**argv)서명 을 갖도록 선택 했는가입니다. 설명은 주로 기록이다 : C는 함께 만들어졌다 유닉스 가지고, 않고 로빙 하기 전에 fork및 (프로세스를 생성하는 시스템 콜이다) execve(프로그램을 실행하는 시스템 콜이다)를, 그 execve송신 어레이 문자열 프로그램 인수 및 main실행 된 프로그램 과 관련이 있습니다. [정보] 더 읽기 유닉스 철학ABI 들.

그리고 C ++은 C의 규칙을 따르고 호환되도록 노력했습니다. mainC 전통과 호환되지 않는 것으로 정의 할 수 없습니다 .

운영 체제를 처음부터 (여전히 명령 줄 인터페이스를 사용하여) 설계하고 프로그래밍 언어를 처음부터 새로 디자인 한 경우 다른 프로그램 시작 규칙을 자유롭게 발명 할 수 있습니다. 그리고 다른 프로그래밍 언어 (예 : Common Lisp 또는 Ocaml 또는 Go)에는 다른 프로그램 시작 규칙이 있습니다.

실제로, main일부 crt0 코드에 의해 호출됩니다 . Windows에서는 각 프로그램이 crt0에 해당하는 글 로빙을 수행 할 수 있으며 일부 Windows 프로그램은 비표준 WinMain 진입 점을 통해 시작할 수 있습니다 . 유닉스에서는 글 로빙이 쉘에 의해 수행된다 (그리고 crt0ABI와 그것이 지정한 초기 호출 스택 레이아웃을 C 구현의 호출 규칙에 맞게 조정하고있다).


12

"포인터에 대한 포인터"로 생각하기보다는 배열과 문자열을 []나타내는 "문자열의 배열"로 생각하는 데 도움이됩니다 char*. 당신이 프로그램을 실행하면, 당신은 그것을 하나 이상의 명령 줄 인수를 전달할 수 있으며, 이러한이의 인수에 반영하는 것은 main: argc인수의 수이며, argv개별 인수에 액세스 할 수 있습니다.


2
+1 이것! 많은 언어에서-bash, PHP, C, C ++-argv는 문자열 배열입니다. 이 중 당신이 볼 때 생각해야 char **또는 char *[]동일하다.
rexkogitans

1

많은 경우에 대답은 "표준이기 때문에"입니다. C99 표준 을 인용하려면 :

— argc의 값이 0보다 크면 argv [0]에서 argv [argc-1]까지 의 배열 멤버는 string에 대한 포인터를 포함해야합니다.이 포인터 는 프로그램 시작 전에 호스트 환경에서 구현 정의 값을 제공합니다.

물론,이 표준화되기 전에이 명령 줄 매개 변수를 저장하는 목적으로, 초기 유닉스 구현에서 K & R C에 의해 이미 사용 (유닉스에서 걱정해야 할 일이 같은 쉘 /bin/bash또는 /bin/sh임베디드 시스템에서가 아니라). K & R의 "The C Programming Language"(110 페이지)의 첫번째 판 을 인용하려면 :

첫 번째 (통상적으로 argc 라고 함 )는 프로그램이 호출 된 명령 행 인수의 수입니다. 두 번째 ( argv )는 문자열 당 하나씩 인수를 포함하는 문자열 배열에 대한 포인터입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.