잘 가르쳐주신 덕분에 느리지만 천천히 정진하고 있습니다.
다름이 아니라 737p에
long long *numPtr;
// 단일 포인터 long long *numPtr의 메모리 주소는 long long **과
같음
allocMemory((void**)&numPtr, sizeof(long long));
이라고 되어 있고
그 위의 설명에
long long *numPtr;의 메모리 주소는 long long **과 같다는게 이해가 안됩니다.
그리고
allocMemory((void**)&numPtr, sizeof(long long)); 에서
(void**) 를 생략한
allocMemory(&numPtr, sizeof(long long)); 도 컴파일 해보면
실행이 잘되는 것 같은데 (void**)는 빼고
allocMemory(&numPtr, sizeof(long long));만 해도 되나요?
마지막으로
이해가 안되면 그냥
"단일 포인터의 메모리 주소는 이중 포인터과 같음"
이라고 외워도 되나요..?
UNIT 34부터 복습하세요.
영어로 읽는 법 익혀야 합니다. 이 그림은 눈 감고 그릴 수 있어야 합니다.
같은 그림을 책 덮고 10번 그리면 됩니다. 그러면서 완전히 이해하세요.
당연히 이 그림도 눈 감고 그릴 수 있어야 합니다.
그림으로 이해해야 합니다. 코드로 이해하려고 하면 시각적이지 않아서 오히려 어렵습니다.
포인터 사용한 코드 보고 그림으로 그려낼 수 있으면 이해가 된 것.
그림으로 못 그리면 이해를 못 하는 것.
이런 함수를 만들었습니다.
그러면 호출하는 쪽에서는 이 함수의 인자 형식을 맞춰서 호출해야 합니다.
long long *numPtr이므로
numPtr은 pointer to long long입니다.
함수에 pointer to long long을 전달하고 싶다면 pointer to long long의 주솟값을 전달해야 합니다.
pointer to long long의 주솟값은 어떻게 표현합니까?
address of pointer to long long입니다.
그러면 numPtr의 타입은 이미 pointer to long long이니까 이건 어떻게 하면 address of pointer to long long으로 바꿉니까?
&numPtr이라고 쓰면 됩니다.
&numPtr을 영어로는 address of pointer to long long이라고 읽습니다. 궁금하면 유튜브에 가서 C 언어 포인터 강좌 영어로 된 거
찾아보세요. 이런 표현이 수도없이 나오는 것을 확인할 수 있습니다.
그러면 포인터를 인자로 받는 함수의 인자는 어떻게 선언해야 합니까?
address of pointer to long long == pointer to pointer to long long
입니다.
address of는 주솟값이므로 pointer to와 호환이 됩니다.
따라서 allocMemory에서는 long long **ptr로 선언하면 됩니다.
그런데 allocMemory를 타입에 상관없이 메모리를 할당하게 만들고 싶습니다.
그러면 long 대신에 범용 타입인 void를 씁니다.
void **ptr로 선언합니다.
allocMemory((void**)&numPtr, sizeof(long long));
numPtr의 타입은 무엇입니까? 영어로 대답하세요. 그림도 영어로 그리세요.
pointer to long long입니다.
allocMemory는 void **ptr이므로 &numPtr로 만들어서 타입을 맞춰줍니다.
하지만 void **과 &numPtr은 타입이 일치하지 않습니다.
따라서 (void **)로 타입 캐스팅을 해서 형을 일치하게 맞춰줍니다.
(void **)을 지워봅시다.
컴파일러 경고를 모두 켜고 보면 다음과 같이 경고가 발생합니다.
main.c:14:17: warning: incompatible pointer types passing 'long long **' to parameter of type 'void **' [-Wincompatible-pointer-types] allocMemory(&numPtr, sizeof(long long)); ^~~~~~~C 언어에서는 경고를 에러로 취급합니다. 경고는 잠재적으로 문제가 있는 코드라는 뜻이고, 보안 문제를 일으키기 쉽기 때문입니다.
incompatible pointer types입니다. 호환 안 되는 포인터 타입이라는 뜻입니다.
따라서 잘못된 코드입니다.
C/C++ 언어에서는 코드를 이렇게 고쳤는데 돌아가는데? 그러면 이렇게 작성해도 되겠네?
--->>> 아주 옛날에 90년대 이전에 개발자들이 흔히 하던 생각이고, 이렇게 잘못 배운 사람들이 성장해서 C/C++ 입문서를 쓰고,
수많은 잘못된 내용을 담은 책들이 탄생했습니다. 베스트셀러에도 여전히 그런 문제가 있고, 그런 분들을 저격하는 블로그 글도
쉽게 찾아볼 수 있습니다.
-. C/C++를 가르치기 전에 생각해 보기, 2023.3.11
https://proofby.ac/teaching-c/
블로그 작성자의 코멘트입니다.
또 UB로 글을 썼습니다
이번에는 UB 이야기만 한 건 아니고, C/C++ 과목을 열거나 책을 쓸 때 확인해 줬으면 하는 내용들을 좀 화난 목소리로 적었습니다
이렇게 외우면 안 됩니다. 이건 타입을 이해한 게 아닙니다.
원리를 이해하세요.
"단일 포인터의 메모리 주소"가 무엇입니까?
long long *numPtr도 단일 포인터이고, &numPtr은 단일 포인터의 메모리 주소일 겁니다.
그러나 한글로 '단일 포인터의 메모리 주소'라고 외우려 하지 말고, &numPtr 타입은
address of pointer to long long으로 영어로 외우세요.
영어로 보면 명확합니다. C 언어 표준에도 이렇게 설명되어 있고, 영어 교재도 다 이렇게 설명합니다.
한국어로 역순 번역해서 이상하게 가르치니까 더 헷갈리고 어렵다고 하는 겁니다.
int *numPtr로 선언했다면 numPtr의 타입은 무엇입니까? pointer to int입니다.
'단일 포인터'라는 표현은 영어로 치면 single pointer인데 C 언어 표준안에는 쓰지도 않는 용어입니다.
double pointer도 마찬가지.
검색 결과로도 알 수 있지만,
pointer to pointer이고, 표준안도 이렇게 쓰고 있습니다. 다만, 사람들이 발음하기 편하게 축약해서 쓰는 표현 정도가
double pointer입니다. 편의상 double pointer라고 쓸 수는 있습니다만, 권장하지 않습니다.
int **ptr이라면 ptr의 타입은 pointer to pointer to int이고,
*ptr로 썼다면 *ptr의 타입은 pointer to int이고
**ptr로 썼다면 **ptr의 타입은 int입니다.
**ptr는 double pointer이고, *ptr은 single pointer라고 생각하는 게 아니라
*를 하나 붙일 때마다 pointer to를 하나씩 소거하면 됩니다.
지금 이 설명을 10번 읽으세요. 그리고 스스로 타입을 소리 내어 말하세요.
그림으로 그리세요.
cdecl.org 사이트에서도 타입을 입력해보세요.
밑에 영어로 뭐라고 되어 있습니까?
pointer to pointer to int입니다.
포인터가 이해가 안 된다고 베스트셀러 C 언어 책 사서 보지 마십시오.
위 블로그 글에서 저격한 책이 베스트셀러입니다. 블로그 들어가면 어떤 책인지 알 수 있습니다.
잘못된 내용 지적하면 댓글 삭제하고, 차단하고, 그런 베스트셀러는 곤란합니다.
현업에서 가장 많이 하는 일이 잘못 배워온 C/C++ 지식을 교정하는 일입니다.
https://sunyzero.tistory.com/225
교수도 C 언어 제대로 모르는 경우가 많습니다.
이 답변은 사이트 관리자(저자 아님)가 작성했습니다.
관리자님
제가 오늘 다음 진도 안나간 대신 하루 종일 날잡고 unit 34에 있는 포인터 파트와 답변에 적어주신 설명글을 계속 정독하였습니다.
덕분에 보이지 않던 것들도 보이게 되고, 머리가 뻥 뚫린 느낌을 받았습니다. 진심으로 감사합니다.
하지만 저 화살표가 무엇을 뜻하는지를 모르겠습니다..
pointer to int = address of int도 이해했고 *numptr의 자료형과 int num1의 자료형이 같다는 것도 이해가 되었고,
그밑에 아주 상세히 감사히 적어주신 설명글도 다 이해가 되었는데 저 화살표만 이상하게 이해가 안됩니다..
그림 34-9를 다시 참고하세요. 그림에서 화살표가 네모 상자에 있으면 메모리 주소에 접근한다는 뜻입니다.
int *numPtr로 선언했고, 예제 코드에서는
numPtr = &num1;
로 대입했습니다. 이걸 설명한 그림입니다.
따라서 numPtr은 pointer to int이고, 이건 int num1로 선언한 변수 num1이 있는 메모리 주소를 가리킵니다.
&num1은 num1의 주소입니다. address of int라고 읽지요.
그러나 &num1은 num1의 메모리 주소, 즉 네모 상자만 가리키는 겁니다.
그리고 그 안에 있는 값은 int num1로 선언한 변수 num1에 있고,
*numPtr처럼 pointer to int에서 pointer to를 제거하면 남는 int, 흔히 말하는 역참조입니다.
num1과 *numPtr은 int 값이라는 의미입니다.
위에 코드를 설명하기 위해 그린 그림입니다.
int *numPtr로 선언했으니까 pointer to int라고 읽고, 이 포인터는 int 값이 저장된 메모리, 네모 상자에 접근하는 것이고,
*numPtr처럼 역참조하면 int 값에 접근하는 것이라는 의미입니다.
예제 코드 주석에 자료형이 일치한다고 써놨습니다.
pointer to int == address of int, 자료형이 일치한다는 뜻이고
오른쪽은 int이니 자료형이 일치한다는 뜻입니다.
num1 == *numPtr, 자료형이 int로 일치한다는 뜻입니다.
C 언어에서는 자료형을 정확하게 읽는 법만 배우면 되고, 이 자료형이 일치하게 해주면 됩니다. C 언어는 자료형이 일치하지 않아도 경고만 표시하고 일단 컴파일이 되고 돌아가게는 만들지만, 올바른 코드는 아닙니다. 현대 언어는 자료형이 일치하지 않으면 컴파일 에러로 처리하고 컴파일이 안 되는데, C 언어는 과거에 만들어진 언어라 이런 부분에서 관대합니다. 그래서 이게 된다고 C 언어 표준에서 허용한 것도 아닙니다. C 언어 표준은 훨씬 나중에 만들어졌습니다.
때문에 많은 교수, 저자들이 C 언어로 이런 코드도 동작한다! -> 자기 해석을 덧붙임 -> 이상한 썰을 푼다... 같은 경우가 많습니다.
경고가 발생하면 에러로 간주하고 경고가 발생하지 않게 코드를 작성하는 게 중요합니다.