포인터 스터디
『프로그래밍 언어 강좌-C,C++,VC 강좌 (go PROG)』 1586번
제 목:[강좌] 포인터 스터디 [1/8] -신경호
올린이:파이사랑(신경호 ) 00/01/26 23:56 읽음:619 관련자료 없음
-----------------------------------------------------------------------------
반 친구들을 위해서 제가 작년에 쓴 자료 입니다.
조잡하고 많이 이상할지도 모르지만, 잘 봐 주세요. ^^;
『배움터-강좌 (go SSCS)』 25번
제 목:[강좌] 포인터 스터디 [1/8] -신경호
올린이:파이사랑(신경호 ) 00/01/23 23:31 읽음: 11 관련자료 없음
-----------------------------------------------------------------------------
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■ 포인터 스터디 [1] ■
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[990129 파이사랑]
◐ 들어가면서…
안녕하세요. 몇회에 걸쳐 반게에 올렸던 글입니다. 별로 좋은 자료는 아니
지만 여기에 있어도 나쁠건 없겠다는 생각에 올리는데, 과연 정말 그럴지는
모르겠네요. ==; (이 부분만 빼고 나머지는 반게의 글과 동일합니다)
틀린 내용이 있을때는 가차없이 re를 눌러 저에게 메일을 주세요.
◐ 컴파일러에 대해…
C 강좌이므로 터보 C 2.0을 기준으로 하겠으나 전 볼랜드 C++ 밖에 없기 때
문에 거기서 테스트 해 본 예제를 올릴 겁니다. 볼랜드 C++ 3.1을 구하고 싶
으시면 메일 주세요. 꼭 필요한 것들만 압축해서 2메가 정도로 만들어 놓은게
있습니다.
◐ 덧붙이는 글…
예제는 꼭 실행해 보세요. 그리고 내용이 좀 많아서 읽다가 지루해 하실 분
도 계실지 모르겠는데, 컴학부라면 당연히 이해해야만 하는 개념입니다. 아무
래도 글 쓰는게 보는거 보다는 훨씬 힘들터인데 글 쓴 사람 생각도 좀 해 주
세요. ^^; (머 제가 자처해서 하는 일이지만…)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■ 1. 포인터란… ■
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◐ 변수와 주소
우리가 흔히 사용하는 변수. 이 변수의 실체에 대해 얼마나 아시나요? 변수
를 이름 그대로 해석해 보면 「변할 수 있는 값」이라고 하네요. 그렇지만 값
이라고 하기에는 조금 무리가 있지요. 하나의 값이 다른 값으로 그냥 바뀔 수
는 없으니까요. 변수라는 것을 좀 더 구체적으로 말하자면 『값을 저장할 수
있는 메모리』라고 할 수 있습니다. 이미 아실테지만, 변수는 메모리의 일부
입니다. 물론 모든 메모리가 변수가 되는건 아니지만 그 가능성은 가지고 있
지요. 우리가 변수를 선언할 때 컴파일러는 자기가 알아서 비어있는 메모리를
찾아 그 메모리를 변수로 사용하게 되는 겁니다.
그렇다면 컴파일러는 어떻게 각 메모리를 구분할까요? 쉽게 생각합시다. 우
리가 집이나 사람을 구분할 때 바로 주소나 주민등록번호 같은 번호를 사용하
지요. (주소는 일부만 번호이긴 하지만… 더 쉽게 아파트의 호수를 생각해 보
세요) 컴퓨터도 마찬가지 입니다. 각각의 메모리에 주소를 붙여 구별을 하는
것이지요. 그리고 컴퓨터의 메모리는 한줄로 이어져 있기 때문에(이를 「선형
메모리」라고 합니다) 연속된 값으로 주소값이 표시 됩니다. 즉 컴퓨터의 메
모리 크기가 100이라고 하면 0부터 시작해서 99까지의 주소가 존재하는 것이
지요. 그런데 컴퓨터는 일반적으로 수백만 이상의 크기를 가지므로 그만큼 주
소값의 범위도 커지게 됩니다. 그래서 흔히 주소값은 4바이트의 long형으로
나타내는 것입니다. (C에서 정수를 표현하는 가장 큰 자료형이지요)
◐ 포인터 상수와 변수
포인터 상수는 별게 아닙니다. (물론 포인터 변수도 별게 아니지만…) 포인
터 상수는 바로 위에서 말씀드린 주소를 말하는 겁니다. 당연히 크기도 4바이
트겠지요? 컴퓨터의 가장 첫번째 메모리의 포인터는 0x00000000이 되는 겁니
다. 그 다음은 0x00000001… 쉽지요? (앞에 0x를 붙인건 16진수라는거 아시지
요?) 그렇다면 포인터 변수는 무엇일까요? 바로 「포인터 상수를 저장할 수
있는 변수」겠지요. 포인터 변수의 크기도 포인터 상수처럼 4바이트 입니다.
char *p; // 포인터 변수를 선언할 때 「*」를 붙이지요
이 p라는 포인터 변수는 포인터를 저장할 수 있습니다. char의 포인터형이
긴 하지만 포인터는 포인터 입니다. 앞에 정해진 자료형이 무엇이든 상관없이
크기는 4바이트인 거지요. 그렇다면 앞에 정해진 자료형은 왜 붙여주는 것일
까요? 흠… 이건 나중에 알아보도록 하고, 그림을 몇개 보도록 하지요.
char ch = 'A'; // 'A'의 코드 값은 65 입니다
char *pc = &ch; // 변수의 주소값을 알아낼 때는 「&」를 사용합니다
// ch의 주소값이 0x0000001A라고 가정합시다
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃ ┏━━━━━━━━━━━━━━━━┓ ┃
┣━━┫ ▼ ┃
┃주소┃ 10 1A 1B 1C 1D 1E 21 22 23 24 25 ┃
┃ ┃ ┳━┳━┳━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
┃ 값 ┃ ┃ ┃65┃ ┃ ┃ ┃ ┃ … ┃1A┃00┃00┃00┃ ┃ ┃
┃ ┃ ┻━┻━┻━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름┃ ch pc ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
컴퓨터가 수를 저장하는 방법은 좀 특이 합니다. 바이트 별로 거꾸로 저장
하는 것이지요. 즉, 0x12345678을 첫번째 바이트에 78, 두번째 바이트에 56,
세번째 바이트에 34, 네번째 바이트에 12를 저장하는 것이지요. 이유는 조금
후에 알아보도록 하겠습니다.
한가지 더. 이 글에 사용되는 모든 주소값은 임의로 붙인 것입니다. 컴파일
러가 마음대로 빈 공간을 찾아 사용하기 때문에 특별히 정해진 값이 있을수가
없기 때문이지요.
int ih = 0x13;
int *pi = &ih;
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃주소┃ 41 42 43 44 45 46 5A 5B 5C 5D 5E ┃
┃ ┃ ┳━┳━━━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
┃ 값 ┃ ┃ ┃13┃00┃ ┃ ┃ ┃ … ┃42┃00┃00┃00┃ ┃ ┃
┃ ┃ ┻━┻━━━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름┃ ih pi ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
long lh = 0x00781253;
long *pl = &lh;
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃주소┃ 65 66 67 68 69 70 7A 7B 7C 7D 7E ┃
┃ ┃ ┳━┳━━━━━━━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
┃ 값 ┃ ┃ ┃53┃12┃78┃00┃ ┃ … ┃66┃00┃00┃00┃ ┃ ┃
┃ ┃ ┻━┻━━━━━━━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름┃ lh pl ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
◐ *연산자의 쓰임새
모든 연산자가 그렇듯이 *와 &도 여러가지 쓰임새를 가지고 있습니다. 우선
*는 곱을 구하기도 하고 위에서 사용된 것 처럼 자료형에 붙어 포인터 변수임
을 나타내기도 합니다. (이 경우는 연산자라고 보기는 좀 어렵지요) 그리고
또 한가지. 「주어진 주소의 값을 읽어내는」 일도 합니다. 다음 프로그램을
실행해 보세요.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ #include <stdio.h> ┃
┃ ┃
┃ void main(void) { ┃
┃ int i; ┃
┃ for (i = 0; i < 100; i++) ┃
┃ printf("%c", *(char *)i); ┃
┃ } ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
실행해 보면 화면이 이상한 문자들만 잔뜩 나올 겁니다. 그럼 이 문자들이
뜻하는게 무엇일까요? 소스 코드를 한번 봅시다. i 값이 0부터 99까지 증가할
테고 printf 문에서 무언가를 출력하라고 하지요. 그 부분만 뜯어보면,
*(char *)i
i가 0인 경우 위의 코드는 다음과 같겠지요.
*(char *)0
이게 뜻하는 의미를 아시겠나요? 우선 0이라는 수(상수)를 char *형으로 캐
스팅 했지요. 이제 0은 그냥 수가 아닌 char 크기(1 바이트)의 메모리를 나타
낼 수 있는 포인터 상수가 된 겁니다. 그리고 거기에 *연산자를 붙여 값을 읽
어내고 있지요. 그렇습니다. 이 소스 코드는 0번 메모리의 값을 읽어서 출력
하는 것이지요. 이렇게 99번째 메모리까지 모두 100개의 메모리의 내용을 보
여주는 코드인 것입니다. 물론 우리가 알아볼 수는 없는 글자들만 잔뜩 있지
만요.
만약에 (char *)로 캐스팅을 안하고 *0이라고만 쓰면 어떻게 될까요? 에러
가 나지요? 왜일까요? *연산자는 그 다음에 오는 내용을 포인터라고 가정을
하기는 하는데, 거기서 얼마만한 크기의 메모리를 읽어와야 하는지를 모르는
겁니다. 즉 *앞의 자료형은 「그 포인터의 값을 *를 사용해서 읽어올 때 얼마
만한 크기를 읽어와야 하는지를 정해주는 것」이라 할 수 있습니다. 다음 예
제를 보세요.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ #include <stdio.h> ┃
┃ ┃
┃ void main() { ┃
┃ int ih = 0x0506; ┃
┃ char *pp = (char *)&ih; ┃
┃ ┃
┃ long lh = 0x01020304; ┃
┃ char *pc = (char *)&lh; ┃
┃ int *pi = (int *)&lh; ┃
┃ ┃
┃ printf("- integer -\n"); ┃
┃ printf("ih=%X\n", ih); ┃
┃ printf("(char)ih=%X, *(char *)=%X\n", (char)ih, *pp); …① ┃
┃ ┃
┃ printf("\n- long integer -\n"); ┃
┃ printf("lh=%lX\n", lh); ┃
┃ printf("(char)lh=%X, *(char *)=%X\n", (char)lh, *pc); …② ┃
┃ printf("(int)lh=%X, *(int *)=%X\n", (int)lh, *pi); …③ ┃
┃ } ┃
┃ ┃
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ <결과> ┃
┃ - integer - ┃
┃ ih=506 ┃
┃ (char)ih=6, *(char *)=6 ┃
┃ ┃
┃ - long integer - ┃
┃ lh=1020304 ┃
┃ (char)lh=4, *(char *)=4 ┃
┃ (int)lh=304, *(int *)=304 ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
①의 첫번째 값은 int형 0x0506을 char형으로 캐스팅한 값입니다. 당연하게
도 int는 2바이트, char형은 1바이트이므로 char형을 벗어나는 부분이 잘려져
나간 6이 결과가 되겠지요.
포인터를 사용한 연산도 마찬가지 입니다. pp는 1바이트 크기의 변수를 포
인트하는 포인터 변수이지요. 이 pp 변수를 사용해 그 메모리를 읽어오게 되
면 그 첫번째 바이트 한 바이트만을 읽어오게 되는 것이지요.
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃주소┃ 41 42 43 44 45 46 5A 5B 5C 5D 5E ┃
┃ ┃ ┳━┳━━━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
┃ 값 ┃ ┃ ┃06┃05┃ ┃ ┃ ┃ … ┃42┃00┃00┃00┃ ┃ ┃
┃ ┃ ┻━┻━━━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름┃ ih pp ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
pp는 char *형 변수이므로 *pp는 char 만큼(1바이트)만을 읽어오게 된다는
것입니다. ih를 char형으로 캐스팅한 결과와 같게 되지요. 당연히 같아야 하
는 거고요.
만약 수를 거꾸로 저장하지 않고 순서대로 저장을 한다면 두 결과가 틀리게
나올 겁니다. 포인터를 사용해 어떤 변수의 일정 크기만큼만을 읽어오려고 할
때 문제가 생기겠지요. 굉장히 복잡한 과정이 요구될 겁니다. 이 때문에 컴퓨
터는 수를 거꾸로 저장하는 것이지요.
②와 ③은 위의 내용과 같은 내용이므로 넘어가도록 하겠습니다.
◐ &연산자의 쓰임새
연산자 &는 비트 연산자로써 AND 연산을 행하는 연산자입니다. 그런데 여기
서는 address-of 연산자의 기능도 행하고 있습니다. 이름 그대로 「어떤 변수
의 주소를 알아내는」 역할을 하는 연산자 입니다. 상수는 메모리에 위치하지
않으므로 주소가 있을 수 없고 당연히 &연산자도 사용하지 못하겠지요.
◐ *연산자와 &연산자의 관계
*연산자는 어떤 주소의 값을 읽어오는 연산자라고 했고 &연산자는 어떤 변
수의 주소를 알아내는 연산자라고 했지요. 이 두 연산자의 기능은 완전히 반
대라고 할 수 있겠지요. 물론 변수에 대해 사용할 때 말입니다. (상수에서는
*는 가능하지만 &는 아예 사용을 하지 못하지요)
◐ 포인터 변수를 사용해 간접적으로 변수의 값 참조하기
int ih = 0x1234;
int *pi = &ih;
*pi = 0x5678;
이미 아시겠지만 간단히 설명하겠습니다. pi는 ih의 주소를 가지고 있고 그
주소의 메모리를 *연산자를 통해 참조하고 있습니다. 그 메모리에 0x5678이라
는 값을 넣고 있지요. 한 문장으로 설명한다면 「pi의 값을 주소로 하는 2바
이트 크기의 메모리에 0x5678를 대입하는」 것입니다. 다음과 같이 한다면 어
떻게 될까요?
int ih = 0x1234;
char *pc = &ih;
*pc = 0x56;
그렇습니다. 바로 ih 값중의 char형으로 참조 가능한 부분인 첫번째 바이트
만이 바뀌게 되지요. 결과적으로 ih는 0x1256이 되는 것입니다. 신기하지요?
◐ void형 포인터
포인터 변수를 선언할 때 * 앞에는 자료형을 정해준다고 했습니다. 만약 다
음과 같이 사용한다면 어떻게 될까요?
int ih = 0x1234;
void *vc = (void *)&ih;
물론 아무런 에러도 나지 않습니다. vc도 포인터 변수이므로 당연하게도 ih
변수의 포인터를 저장할 수가 있는 것입니다. 다음은 어떨까요.
*vc = 0x5678;
이 코드도 에러가 나지 않을까요? 조금만 생각해 보면 알 수 있지요. 당연
히 에러가 발생 합니다. void형은 크기를 갖지 않기 때문에 얼마만한 크기 만
큼의 메모리에 값을 넣어야 할지를 모르는 거지요. 다음과 같이 사용하면 되
겠지요.
*(int *)vc = 0x5678;
이렇듯 void형 포인터는 어떤 포인터 값도 가질 수 있지만, 직접적으로 참
조를 할 수는 없고 반드시 캐스팅을 해 주어야 하는 포인터 입니다. 별로 사
용될 곳이 없어 보이기도 하지만 나중에 프로그래밍을 하다보면 의외로 쓰이
는 곳이 많기도 하지요.
◐ 참조에 의한 호출 (참조 호출, Call by reference)
우선 Call by value를 알아 봅시다. 우리가 일반적으로 사용하는 함수 호출
방법이라고 들었지요? 다음을 봅시다.
int i = 3;
printf("%d", 3); //…①
printf("%d", i); //…②
①과 ②는 어떻게 다를까요? 우리가 보기에는 좀 달라 보이지만, 컴파일러
가 컴파일을 마친 이후에는 완전히 동일한 코드가 됩니다. 다시 말해서 ②번
의 경우 i 대신에 i가 가진 값인 3을 대치시켜서 넘겨준다는 것이지요. 바로
이것이 Call by value 입니다. 변수가 넘어가는 것이 아닌 변수가 가지고 있
는 「값」이 넘어가기 때문이지요.
그렇다면 Call by reference는 무엇일까요? Call by value와 비교해 생각해
봅시다. (이 예제 또 쓰게 되네요… ==;)
void swapA(int a1, int a2) {
int ta;
ta = a1;
a1 = a2;
a2 = ta;
}
void swapB(int *b1, int *b2) {
int tb;
tb = *b1;
*b1 = *b2;
*b2 = tb;
}
흔히 보던 함수지요? 이 함수는 다음과 같이 사용합니다.
int i = 3, j = 4;
swapA(i, j); //…③
swapB(&i, &j); //…④
이후의 결과는 아시다시피 i와 j값이 바뀌어 있겠지요. (swapB 함수만 제대
로 동작하니까요) 그럼 내부적으로 어떤 과정을 통하길래 이런 것이 가능할까
요? 그림을 보도록 하지요. (그림 크기의 한계상 포인터와 int형의 크기를 모
두 1이라고 하겠습니다)
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃③-1 초기 상태 (i, j 선언과 정의) ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃ ┃ ┃ ┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃③-2 [swapA(i, j)] = [swapA(3, 4)] ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃ 3┃ 4┃ ┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j a1 a2 ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃③-3 [int ta = a1] = [int ta = 3] ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃ 3┃ 4┃ 3┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j a1 a2 ta ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃③-4 [a1 = a2] = [a1 = 4] ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃ 4┃ 4┃ 3┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j a1 a2 ta ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃③-5 [a2 = ta] = [a2 = 3] ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃ 4┃ 3┃ 3┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j a1 a2 ta ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃③-6 swapA 함수 종료 (swapA의 변수가 없어짐) ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃ 4┃ 3┃ 3┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
결국 이 함수는 swapA의 두개의 인자의 값만 바꿔주는 역할을 했지 호출한
쪽의 i, j 변수의 값을 바꾸지는 못했습니다. (변수가 없어진다는 것은 이름
이 없어진다는 것을 말합니다. 해당 메모리의 값이 0으로 되거나 하는 것은
아닙니다) 그럼 swapB 함수를 보도록 하지요.
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃④-1 초기 상태 (i, j 선언과 정의) ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃ ┃ ┃ ┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃④-2 [swapB(&i, &j)] = [swapB(30, 31)] ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃30┃31┃ ┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j b1 b2 ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃④-3 [int tb = *b1] = [int tb = *30] = [ing tb = i] = [int tb = 3] ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 3┃ 4┃ ┃ ┃… …┃ ┃30┃31┃ 3┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j b1 b2 tb ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃④-4 [*b1 = *b2] = [*30 = *31] = [i = j] = [i = 4] ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 4┃ 4┃ ┃ ┃… …┃ ┃30┃31┃ 3┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j b1 b2 tb ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃④-5 [*b2 = tb] = [*31 = tb] = [j = tb] = [j = 3] ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 4┃ 3┃ ┃ ┃… …┃ ┃30┃31┃ 3┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j b1 b2 tb ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┃ ┃
┃④-6 swapB 함수 종료 (swapB의 변수가 없어짐) ┃
┃┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓┃
┃┃주소 28 29 30 31 32 33 79 80 81 82 ┃┃
┃┃ ━┳━┳━┳━┳━┳━┳━┳━ ━┳━┳━┳━┳━┳━ ┃┃
┃┃ 값 …┃ ┃ ┃ 4┃ 3┃ ┃ ┃… …┃ ┃30┃31┃ 3┃… ┃┃
┃┃ ━┻━┻━┻━┻━┻━┻━┻━ ━┻━┻━┻━┻━┻━ ┃┃
┃┃이름 i j ┃┃
┃┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
아시겠지요? b1, b2 포인터를 사용해 i, j의 값을 직접 바꾸는 결과가 나타
났습니다. 다음을 한번 볼까요.
void swapC(long c1, long c2) {
int temp;
temp = *(int *)c1;
*(int *)c1 = *(int *)c2;
*(int *)c2 = temp;
}
어떤 생각이 드세요? swapB 함수와 이 함수가 다를까요? 같을까요? 이미 예
상을 하셨겠지만(같은거니까 물어보겠죠? ^^) 완전히 동일한 결과를 나타냅니
다.
어떻게 이렇게 사용이 가능할까요? swapB 함수에서 가장 중요한 부분이 뭔
지 아세요? 인수로 포인터를 받는다고요? 아닙니다. 인수의 타입은 포인터이
든 아니든 간에 4바이트의 메모리를 가질 수 있는 크기라면 뭐든지 가능 합니
다. int형이 4바이트인 컴퓨터에서는 long이 아닌 int형도 가능하다는 것이지
요. 가장 중요한 부분은 바로 인수로 받은 두 값을 참조할 때 *연산자를 사용
한다는 겁니다. 사실상 변수 선언시에 쓰이는 *는 그렇게 큰 의미가 없다는
거지요. 때문에 void *형은 long형과 동일하다고 생각하셔도 됩니다. 포인터
값을 가질수는 있지만 캐스팅 없이는 참조가 불가능한… 아셨죠? (포인터는
4바이트 크기의 상수라는 것만 확실히 아신다면 어려울게 없지요)
이렇게 인자로 받은 두 값을 참조할 때는 그걸 포인터로 가정하고 그 크기
를 정해주기 위해서 int *형으로 캐스팅을 하지요. 이렇게 캐스팅된 포인터
상수에서부터 값을 참조하는 *연산자가 이 함수의 핵심이지요.
그리고 ④-2를 보면 swapB(&i, &j)가 swapB(30, 31)로 바뀐다고 나와 있지
요. 그렇습니다. 이것이 바로 Call by reference이지만 이것도 사실상은 포인
터의 값(!)만을 넘겨주는 Call by value인 것이지요.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◐ 첫번째 스터디가 끝났습니다. 다음 스터디를 기대해 주세요. 이해가 안 되
시는 내용이나 궁금한 사항이나 하여튼 아무 말이라도 하고 싶으시면 언제
라도 메일 주세요. 다음 시간에는 말씀드린대로 문자열과 포인터에 대해서
하겠습니다. 곧 C++ 스터디도 할까 합니다…
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
『프로그래밍 언어 강좌-C,C++,VC 강좌 (go PROG)』 1587번
제 목:[강좌] 포인터 스터디 [2/8] -신경호
올린이:파이사랑(신경호 ) 00/01/26 23:57 읽음:229 관련자료 없음
-----------------------------------------------------------------------------
『배움터-강좌 (go SSCS)』 27번
제 목:[강좌] 포인터 스터디 [2/8] -신경호
올린이:파이사랑(신경호 ) 00/01/23 23:35 읽음: 3 관련자료 없음
-----------------------------------------------------------------------------
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
■ 2. 배열과 포인터 ■
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◐ C에서의 문자열
C에서 문자열은 포인터를 사용해서 구현된다는 것은 다 아실겁니다. 정말로
그런지 한번 살펴보도록 하지요. 다음 문장을 보세요.
char *sp = "Love";
┏━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃주소┃ 41 42 43 44 45 46 5A 5B 5C 5D 5E ┃
┃ ┃ ┳━┳━┳━┳━┳━┳━┳━━━━━━┳━━━━━━━┳━┳ ┃
┃ 값 ┃ ┃ ┃L ┃o ┃v ┃e ┃\0┃ … ┃42┃00┃00┃00┃ ┃ ┃
┃ ┃ ┻━┻━┻━┻━┻━┻━┻━━━━━━┻━━━━━━━┻━┻ ┃
┃이름┃ sp ┃
┗━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
제가 항상 포인터를 그 포인터가 가리키는 메모리 영역보다 뒤에 그리는데
반드시 그런건 아닙니다. 포인터 변수도 역시 변수이기 때문에 컴파일러가 알
아서 비어있는 곳에 메모리를 할당하는 것이지요.
위의 예제를 보면 그 동안의 예제와는 다르게 "Love"의 첫 부분에 따로 이
름이 붙어있지 않지요? 바로 그렇습니다. C에서는 문자열 자체에 이름을 붙일
수는 없는 거지요. 그 포인터를 통해서만 참조가 가능한 겁니다. 다시 말씀드
려서 C에서의 문자열은 포인터가 전부라는 얘깁니다. 그러니까 문자열의 길이
도 일반적인 방법으로는 알아낼 수가 없지요. 따로 길이를 저장하는 공간이
없으니까요.
그래서 C에서 사용하는 방법이 바로 문자열 끝에 0이라는 값을 넣어주는 것
입니다. 여기서 0이란 것은 코드값이지 문자로 표현되는 '0'이 아닙니다. 둘
이 어떻게 다른건지는 아시겠지요? (문자 '0'의 코드는 0x30 입니다, 10진수
로는 48이지요)
결국 문자열 중간에 0이라는 코드를 가지는 문자는 넣을 수 없게 되겠지요?
그러나 걱정할 것 없습니다. 코드 0은 사용되지 않는 문자이기 때문이지요.
그럼 이 문자열을 어떻게 다루지요?
puts(sp);
이렇게하면 문자열을 출력하는게 되지요? 그런데 이상한 점이 있지요. 여태
까지는 포인터가 가리키는 내용을 출력할려면 *연산자를 붙여야 했었는데 여
기서는 그러질 않네요. 왜 그럴까요? 그냥 당연히 그런거다 생각하시나요? ^^
그렇지는 않지요. 사실상은 puts 함수 안에서 인자로 받은 포인터에 *를 붙
여서 내부적으로 한 문자씩 출력하기 때문이지요. 문자열을 다루는 함수들이
내부적으로 어떤 과정을 거치는지는 잠시 후에 알아보기로 합시다.
◐ 문자열과 배열
아시다시피 배열과 포인터는 매우 가까운 관계 입니다. 그러니 당연히 배열
과 문자열도 밀접한 관계를 가지고 있겠지요. 우리는 배열의 참조 연산자인
[]를 사용해서 문자열에도 접근 할 수 있었습니다.
sp[0]은 'L'이고 sp[1]은 'o'겠지요. 이 정도는 아시리라 믿고…