unit 39. 문자열 사용하기 관련 질문입니다.
char *p = "hello";
printf('%p", p); // 문자열 hello가 저장된 메모리 공간의 주소값 출력
printf("%s", p); // 문자열 hello 출력
printf로 %s 키워드를 통해 문자열 hello가 나오는게 이해가 안갑니다. 포인터 p는 문자열 hello가 저장된 공간의 주소값을 가지고 있는거잖아요, 그 저장된 공간에 들어있는 문자열을 가져오기 위해서는 printf("%s", *p); 이렇게 역참조를 해서 그 공간에 들어있는 값을 가져와야 hello가 출력되는 거 아닌가요?
%s를 썼을 때 기대하는 타입은 char*입니다.
char *p = "hello";
이 경우 p의 타입은 char*이므로 p만 쓰면 됩니다.
문자열이 시작하는 주소를 요구하는 것이므로 p만 쓰면 됩니다.
p의 타입은 pointer to char이고, *p의 타입은 char입니다.
역참조라는 것은 영어로 타입을 읽을 때 pointer to를 제거하는 것이죠.
%s가 요구하는 타입은 char*, pointer to char이고,
*p를 쓰면 char를 넘기는 것이니 경고가 발생합니다.
UNIT 34.2의 내용입니다. 이 내용부터 복습하면 좋습니다.
printf로 출력할 때 서식 지정자와 타입을 맞춰주는 겁니다.
%d, %i 서식 지정자는 부호 있는 정수를 출력할 때 사용합니다.
int로 선언한 변수나 리터럴 정수 값을 직접 지정할 수 있습니다.
int age = 20;
printf("%d\n", age);
%d는 정수를 기대하고, int는 정수입니다. 그러니 경고가 발생하지 않습니다.
printf("%d\n", 20);
이렇게 쓰는 것도 가능합니다.
%d는 정수를 기대하고, 20는 정수 리터럴이니까요.
int*은 포인터입니다. 타입으로 읽을 때는 영어로 읽는 법을 배우세요.
pointer to int 타입이라는 뜻입니다.
int age = 20;
int *ref = &age;
printf("%d\n", *ref);
age는 int 타입입니다.
int* ref로 선언했으니 ref는 pointer to int 타입입니다.
pointer to = 가리키는
int = int를
pointer to int = int를 가리키는
이라는 뜻입니다.
int *ref = age;
이렇게는 쓸 수 없습니다. age의 타입은? int
ref의 타입은? pointer to int
타입이 다른데 =를 쓸 수 있나? 없습니다. C는 관대하니 허용하고 경고를 발생시키겠지만, 경고는 에러로 간주해야 합니다.
흔히 pointer to int이니까 주솟값이라고 하죠? int가 있는 곳을 가리키는 주솟값이다... 라고 하는데, 이런 설명이 보통은 더 복잡하게 만듭니다.
pointer to int라고 읽으면 되고, pointer to가 붙어 있으니 주소구만 하면 됩니다.
&는 address of 연산자입니다. '참조' 연산자라는 말로 쓰이지만, 용어는 정확하게 배워두는 게 좋습니다. 영어로 익혀두세요.
&age는 address of int라는 뜻입니다.
pointer도 주소(address)이므로 이 둘은 호환이 됩니다.
int *ref = &age;
이게 가능한 이유는?
ref = pointer to int
&age = address of int
둘은 모두 주소를 다룹니다. 그러니까 이 문장은 올바릅니다.
#include <stdio.h>
int main(void) {
int age = 20;
int *ref = &age;
printf("%d\n", *ref);
return 0;
}
실행하면 결과도 잘 나옵니다.
printf에서는 %d를 썼는데, 이는 정수를 기대합니다.
printf("%d\n", ref);
라고 쓰면 안 됩니다.
%d는 정수를 원하고, ref의 타입은 pointer to int이므로 정수가 아닙니다.
처음 질문으로 돌아가면 %s는 문자열을 원합니다.
C 언어에서는 문자열 타입이 없고, 문자 배열로 처리합니다.
char *msg = "Hello, World";
msg는 pointer to char이고, 문자열의 끝은 널 문자(\0)로 처리합니다.
끝에 항상 널 문자가 자동으로 들어갑니다.
printf("%s\n", msg);
%s는 문자열에 사용합니다. msg는 pointer to char 타입이고, 문자열입니다.
C++, Java 같은 언어처럼 string, String 타입이 따로 있지 않습니다.
pointer to char로 문자열이 시작하는 위치(주소)를 받아서 널 문자열이 있을 때까지 문자열을 출력합니다.
%s는 문자열이 시작하는 주소를 기대합니다. 그걸 C 언어는 pointer to char로 표현할 뿐입니다.
printf("%c\n", msg);
이건 허용되지 않습니다.
main.c:6:20: warning: format specifies type 'int' but the argument has type 'char *' [-Wformat] printf("%c\n", msg); ~~ ^~~ %s 1 warning generated.msg는 char * 타입이니까 %s를 쓰라는 경고가 표시됩니다.
(모든 경고를 켜는 -Wall 옵션을 줘야 합니다, GCC, Clang 기준)
C 언어는 정적 타입 언어이고, 타입을 맞춰주는 게 가장 중요합니다. 다른 타입을 넣으면 경고가 발생합니다.
printf("%c\n", *msg);
msg에는 주소가 있으니까 *로 역참조를 하면 어떻게 되는가?
*msg로 쓰면 H가 출력됩니다.
%c가 원하는 건 문자, char 1개입니다. 그러니 여기에 부합합니다.
printf("%c\n", *(msg+1));
이렇게 하면 e가 출력됩니다.
*(msg+1)은 msg[1]과 같습니다.
처음에 *msg는 *(msg + 0)과 같은데 0은 생략할 수 있죠. 그러니 *msg만 써도 첫 번째 문자 H가 출력됩니다.
%p는 pointer type을 원합니다. 주솟값을 받아서 16진수로 출력하죠.
msg의 타입은 pointer to char이므로 주솟값입니다.
printf("%p\n", msg);
라고 쓰면 됩니다.
다만, 모든 경고를 켜는 -Wall을 켜면 값은 출력되지만, 경고도 같이 발생합니다.
0x402004
main.c:6:20: warning: format specifies type 'void *' but the argument has type 'char *' [-Wformat-pedantic] printf("%p\n", msg); ~~ ^~~ %s 1 warning generated.
void *는 UNIT 34.5에 설명되어 있고, 그림 34-24를 보면 됩니다.
printf("%p\n", (void*)msg);
%p가 원하는 타입은 범용 포인터인 void *이므로 (void *)로 형변환을 해주면 경고가 발생하지 않습니다.
이거 이상은 자세히 설명하기가 어렵습니다.
UNIT 34부터 복습하고, 책 전체를 한 번 끝내고 다시 복습하는 것을 추천합니다. 세 번 정도 읽는 게 좋습니다. 처음 읽을 때는 이해 안 되는 건 건너 뛰는 것도 괜찮습니다.
참조, 역참조... 이렇게 많이 쓰기는 하는데, 타입은 영어로 읽는 것을 배우는 게 좋습니다.
타입 읽는 법을 알려주는 사이트입니다.
declare msg as pointer to char라고 설명됩니다. msg의 타입은 pointer to char라는 뜻입니다.
&는 address of라고 읽는다고 했습니다.
&의 정확한 명칭은 주소 연산자이고,
C 언어에는 '참조', 즉 reference라는 개념이 없습니다.
참조, reference 개념이 있는 것은 C++, Java 같은 언어입니다.
C 언어 스펙 6.5.3.2입니다.
Address and indirection operators라고 되어 있습니다.
*는 indirection operator가 정확한 명칭이고, '간접 참조 연산자'라고 합니다. dereferencing이라고 쓰다보니 '역참조'라고 번역되어 소개되어 있습니다만, 이는 참조 개념이 있는 C++, Java에 적합한 표현입니다.
C 언어에서는 참조 연산자가 없습니다.
C++에서 &는 참조 연산자이지만, C에서는 주소 연산자입니다.
int age = 20;
int& ref = age;
C++에서는 int&이 왼쪽에 올 수 있습니다. 이는 reference to int라는 뜻입니다.
반면에 C에서는 컴파일 에러입니다. 경고가 아니라 에러입니다. C 언어에는 왼쪽에 int&처럼 타입으로 선언해서 쓸 수 없습니다.
C에서 &는 주소 연산자이기 때문에
int* ref = &age;
처럼 오른쪽에 주소 연산자로만 쓸 수 있습니다.