C 프로그래밍및실습 9. 포인터 세종대학교 목차 1) 포인터란? 2) 배열과포인터 3) 포인터연산 4) 함수와포인터 5) * void 포인터 6) * 함수포인터 2
1) 포인터란? 메모리 프로그램이실행되기위해필요한정보 ( 값 ) 을저장하는공간 1 byte (8 bits) 단위로물리주소가부여되어있음 개념적으로, 메모리는일렬로연속되어있는크기가 1byte 인방들의모음이라고볼수있음 포인터 : 메모리의각방에부여된주소 일반적으로주소의길이는 4 bytes 이고, 주소는 16 진수로표현 메모리주소 0x003BDC96 0x003BDC97 0x003BDC98 0x003BDC99 0x003BDCA0 0x003BDCA1 0x0033BDCA 0000 1101 0100 1010 0000 0001 0000 0000 0001 0010 1111 1110 1110 1101 메모리에저장된값 메모리의일부분예 (byte 단위 ) 3 1) 포인터란? 변수와메모리의관계 변수는선언될때, 메모리에그변수를위한공간이할당됨 ( 주의 ) 변수에할당되는메모리의주소는시스템마다다르다. 주소연산자 (&) : 변수에할당된메모리공간의시작주소값을구해줌 int a 0x1a; // 0x 는 16 진수를표현, 0x1a 26 printf("%d%#x...%#x",a,a,&a); // %#X 는 16 진수로출력 260x1a...0X3BDC97 주소 0x003BDC96 0x003BDC97 0x003BDC98 0x003BDC99 0x003BDCA0 0x003BDCA1 0x0033BDCA 0000 1101 0000 0000 0000 0000 0000 0000 0001 1010 1111 1110 2 1110 1101 a 에할당된메모리공간 (4bytes) : 한번할당되면고정됨 4
1) 포인터란? C프로그램에서변수의의미는 (2가지) 1. 그변수에할당된공간을의미 ( 공간이주소를뜻하는것은아님 ) 선언 or 대입문의왼쪽변수 (l-value) 에서사용될때 2. 그변수에저장된값을의미 대입문의오른쪽변수 (r-value), 조건문, 함수의인수로사용될때 char c1, c2; // c1, c2를위한공간을메모리에할당 c1 c2; // c2를 c1에저장 c2에저장된값을 c1의공간에저장 if( c1 < c2 ) // c1이 c2보다작으면 // c1에저장된값이 c2에저장된값보다작으면 printf("%c",c1); // c1을인수로넘겨라 c1에저장된값을넘겨라 주소 0x003BDC96 0x003BDC97 0x003BDC98 0x003BDC99 0x003BDCA0 0000 1101 0000 0000 0000 0000 0000 0000 0001 1010 c1 c2 5 1) 포인터란? ( 실습 1) ( 실습 1) 아래와같이변수들의주소값을출력해보자. 출력 char a, b; int i, j; double x, y; a: 0x18F91F, b: 0x18F91E i: 0x18F924, j: 0x18F920 x: 0x18F930, y: 0x18F928 메모리 주소 ( 마지막두자리만표시함 ) 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30... 37... b a i j y x 6
1) 포인터란? ( 실습 2) ( 실습 2) 아래와같이배열원소들의주소값을출력해보자. 출력 int x[5]; x[0]: 0x1FFEC8 x[1]: 0x1FFECC x[2]: 0x1FFED0 x[3]: 0x1FFED4 x[4]: 0x1FFED8 메모리 주소 ( 마지막두자리만표시함 ) C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC x[0] x[1] x[2] x[3] x[4] 7 1) 포인터란? 포인터 ( 자료형 ) 주소값을나타내는특수자료형 일반적으로변수의주소를저장하며, 변수를가리킨다 라고표현함 주소는숫자이므로, 기본적으로정수를저장하지만, int( 정수형자료형 ) 와구별되어처리됨 따라서선언할때포인터임을명시해야한다. 참고 : 주소값을출력할때 printf 문서식에 %p 를사용 %d 또는 %x 도가능하나컴파일경고발생 8
1) 포인터란? 선언 포인터라는표시 + 기존의자료형표시 어떤자료형의포인터 ( 주소 ) 인가에따라, 정수형포인터 ( 정수형을가리키는 ), 실수형포인터 ( 실수형을가리키는 ) 등이있음. 선언방법 단지변수명앞에 * ( 참조연산자 ) 만덧붙이면된다 예 ) char *pch; int *pnum; pch 는문자형포인터 ( 변수 ) 이고 pnum 은정수형포인터 ( 변수 ) pch 와 pnum 은똑같이주소를저장하는변수이지만대상의자료형이다르기때문에다른자료형으로취급 9 1) 포인터란? 선언 참조연산자 (*) 를자료형쪽에붙이기도함 ( 오른쪽형태 ) char int *pch; *pnum; char* int* pch; pnum; 그러나일반적으로왼쪽형태를더많이사용 포인터변수와일반변수를동시에선언 int *pnum1, num110, *pnum2, num2, arr[10]; int 포인터변수 pnum1, pnum2 선언 int 변수, num1, num2 선언하고, num1은 10으로초기화 int 배열 arr 선언 10
1) 포인터란? 선언 ( 초기화 ): 선언과동시에초기화하기 int num, *pnum # pnum 을선언하고 num 의주소로초기화 ( 주의!!) 위두변수의순서를바꾸면컴파일오류발생 int *pnum &num, num; // 컴파일오류 num 의주소값을알기위해서는 num 이먼저선언되어야함 11 1) 포인터란? 대입 ( 연결 ) 포인터 ( 변수 ) 에는주소값만대입될수있다. char ch'a'; char *pch; int num3, *pnum; pch &ch; pnum # // 두줄을묶어서 // char ch'a',*pch; 로적어도됨 // 변수 ch의주소가 pch에저장됨 // 변수 num의주소가 pnum에저장됨 printf("%c %p\n",ch, pch); // 주소출력 : %p printf("%d %p\n",num, pnum); pch 0x3C pnum 0x42 주소 0x3C ch 'A' 0x42 num 3 변수이름 변수값 12
1) 포인터란? 대입 ( 연결 ) 포인터 ( 변수 ) 에주소를대입하여특정변수와연결시키는것을 " 가리킨다 " 라고표현하고, 메모리그림에서는화살표 로표시 pch &ch; // 포인터 pch에변수 ch를연결시킴 pnum # // 포인터 pnum에변수 num을연결시킴 " 포인터 ( 변수 ) pch 가변수 ch 를가리킨다." pch 0x3C pnum 0x42 0x3C ch 'A' 0x42 num 3 13 1) 포인터란? 참조연산 포인터 ( 변수 ) 가가리키는변수에접근하는것 참조연산자 * ( 간접연산자, 포인터연산자라고도부름 ) 를사용예 ) *pch : 포인터 pch가가리키는변수, 0x3c번지에저장된값 char ch'a', *pch; int num3, *pnum; pch &ch; pnum # printf("%c %p\n", *pch, pch); printf("%d %p\n", *pnum, pnum); A 001EA03C 3 001EA042 pch 0x3C 0x42 pnum 0x3C ch 'A' 0x42 num 3 14
1) 포인터란? 참조연산자를이용한대입 예 ) ch'b' 와 *pch'b' 는동일한기능을한다. 전자는직접접근, 후자는간접접근 char ch'a', *pch; int num3, *pnum; pch &ch; pnum # *pch 'B'; *pnum 5; printf("%c\n", ch); printf("%d", num); pch 0x3c 0x42 0x3c pnum pch 0x3C 0x42 'A' ch num 3 0x3C ch 'B' B 5 pnum 0x42 0X42 num 5 15 1) 포인터란? ( 실습 3) ( 실습 3) 다음에해당하는문장들을차례로작성하고, 메모리그림을그려보시오. int 변수 num1, num2 선언 int 포인터변수 p 선언및 num2 의주소로초기화 num1 에 3000 을넣는다 num1 의주소를 p 에넣는다. num2 에 p 가가리키는변수의값을넣는다. num1, num2, p 를출력한다. num1 의주소, num2 의주소, p 의주소를출력한다. 16
1) 포인터란? #include<stdio.h> void main(void) { int num1, num2, *p&num2; num13000; p&num1; num2*p; 0x36 0x2F p 0x2F num1 3000 0x20 num2 3000 } printf(" 값 : num1%d num2%d p%p\n",num1,num2,p); printf(" 주소 : num1%p num2%p p%p\n",&num1,&num2,&p); 포인터를이해하고학습하기위한가장좋은방법은메모리그림을그리는것이다!! 17 1) 포인터란? 포인터주의사항 1 ( 초기화 ) 선언후연결없이바로사용하면? pnum??? int *pnum ; *pnum 9 ; // pnum에는쓰레기값 // 실행 ( 런타임 ) 에러발생 int *pnum, num; pnum # // 반드시어떤변수에연결후사용 *pnum 9; 널 (NULL) 포인터 아무것도가리키지않음을표시하기위해사용 실제로는 0의값을가지므로조건문에서사용하면거짓에해당된다. pnum NULL; 일반변수를 0으로초기화하는것과비슷한목적으로사용 18
1) 포인터란? 포인터주의사항 2 ( 주소, 참조 ) & ( 주소연산자 ) 는포인터를포함한모든변수에사용가능 포인터변수도변수다 ( 즉, 메모리에공간이할당됨 ) * ( 참조연산자 ) 는포인터변수에서만가능 int num9, *pnum # printf("%p %p %p\n", &pnum, pnum, *pnum); printf("%p %p %p\n", &num, num, *num); // 컴파일오류첫번째 printf문의결과는? 0x9A 0x72 pnum 0x72 9 num 19 1) 포인터란? 포인터주의사항 3 ( 대입 ) 포인터변수의자료형은연결된변수의자료형과일치해야한다. int 포인터형변수에 char 형변수의주소를대입하면? char ch'a', *pch; int num3, *pnum; pch # // 컴파일경고 ( 또는오류 ) pnum &ch; // 컴파일경고 ( 또는오류 ) *pch 66; *pnum 'a'; // 정상실행되나, 바람직하지않음 // 실행오류발생위험 printf("%c\n", *pch); // ch 에저장된값출력 printf("%d\n", *pnum); // ch 에저장된값출력 위코드를이해하기위해메모리그림을그려보자. 20
1) 포인터란? 포인터주의사항 3 ( 대입 ) 다른예 char ch'a', *pch &ch; int *pnum; pnum pch; // 컴파일경고 ( 또는오류 ) printf("%c\n", *pch); // ch 에저장된값출력 printf("%d\n", *pnum); // ch 에저장된값출력 동일하지않은자료형의포인터간대입은문법적으로허용이되기도하나, 프로그램오류의원인이된다. 21 1) 포인터란? 포인터의크기 주소값의크기와동일 ( 시스템마다다를수있다 ) sizeof 연산자를이용하여확인해보자. char *pch; int *pnum; double *pdnum; printf("%d,", sizeof(pch)); printf("%d,", sizeof(pnum)); printf("%d\n", sizeof(pdnum)); 4,4,4 포인터의종류 ( 자료형 ) 에관계없이주소값을저장하기위해필요한공간은동일 22
1) 포인터란? 이중포인터 포인터변수를가리키는포인터변수 int x 5; int * ip; int ** ipp; // 이중포인터선언 ip &x; ipp &ip; printf("%d %d %d", x, *ip, **ipp); 실행결과 5 5 5 삼중포인터 이중포인터를가리키는포인터변수 다중포인터 23 2) 배열과포인터 r-value로사용시배열이름의비밀 일반변수의이름은? (r-value로사용시, 예 : 대입문오른쪽 ) 변수에저장된값을의미 변수의주소를얻기위해서는주소연산자 (&) 사용 int a 9; printf("%d %p", a, &a); // a 에저장된값, p 의주소 배열이름은배열의시작주소를의미한다. 아래에서 b 와 &b 는동일 int b[10] {0}; printf("%p %p", b, &b); // 둘다배열 b 의주소 001ce2f0 001ce2f0 24
일반변수와배열비교 2) 배열과포인터 일반변수 int i9, *ip &i; i : 변수 i에저장된값 ip : 변수 ip에저장된값 &i : 변수 i의주소 &ip : 변수 ip의주소 0x40 ip 0x52 0x52 i 9 배열 int ar[5]{2, 3, 5, 7, 11}; ar[2] : 원소 ar[2] 에저장된값 ar : 배열 ar 의시작주소 &ar[2] : 원소 ar[2] 의주소 &ar : 배열 ar 의시작주소 ( 잘안쓰임 ) ar 0xB4 0xB8 0xBC 0xC0 0xC4 2 3 5 7 11 ar[0] ar[1] ar[2] ar[3] ar[4] 배열원소는일반변수와동일하게취급됨 25 2) 배열과포인터 주소를이용한배열참조 배열이름은주소값을의미하므로, 참조연산자와함께사용가능 ar : 배열의시작주소 *ar : 배열의시작주소에저장된값, 즉, 0 번째원소를의미 int ar[5]{2, 3, 5, 7, 11}; printf("%p %d %d\n", ar, ar[0], *ar); 001E40B4 2 2 ar 0xB4 0xB8 0xBC 0xC0 0xC4 2 3 5 7 11 [0] [1] [2] [3] [4] 26
2) 배열과포인터 배열주소에대한증감연산 (ar의값이 0xB4일때 ) ar+1 의결과는? 0xB5? 0xB8? *(ar+1) 의결과는? int ar[5]{2, 3, 5, 7, 11}; printf("%p %d %d\n", ar+1, ar[1], *(ar+1)); 001E40B8 3 3 ar 0xB4 0xB8 0xBC 0xC0 0xC4 2 3 5 7 11 [0] [1] [2] [3] [4] 27 2) 배열과포인터 배열주소에대한증감연산은주소가가리키는변수의크기단위로 ( 즉, 배열원소하나의크기만큼 ) 증가 or 감소 int 배열의경우 : 4 ar+i : 배열 ar의 i 번째원소의주소 *(ar+i) : 배열 ar의 i 번째원소의값, 즉, ar[i] ar+0 ar+1 ar+2 ar+3 ar+4 ar 0xB4 0xB8 0xBC 0xC0 0xC4 2 3 5 7 11 [0] [1] [2] [3] [4] 28
2) 배열과포인터 ( 실습 4) 문자배열과실수배열을선언하고, 다음을출력하라. char car[5]{'h','e','l','l','o'}; double dar[5]{1.1, 2.2, 3.3, 4.4, 5.5}; car, car[0], *car car+1, car[1], *(car+1) car+2, car[2], *(car+2) dar, dar[0], *dar dar+1, dar[1], *(dar+1) dar+2, dar[2], *(dar+2) 주소값의변화량을주의해서살펴보자 29 2) 배열과포인터 중간정리 배열의시작주소를이용하여배열을포인터형태로사용할수있다. 원소의주소 원소의값 ar ar+0 ar+1 ar+2 ar+3 ar+4 0xB4 0xB8 0xBC 0xC0 0xC4 2 3 5 7 11 ar[0] ar[1] ar[2] ar[3] ar[4] *(ar+0) *(ar+1) *(ar+2) *(ar+3) *(ar+4) 주소의증감단위가변수의크기에의해결정되기때문에, 배열의자료형에관계없이다음이성립한다. *(ar+i) ar[i] 30
2) 배열과포인터 배열을포인터변수에연결하여사용 배열이름은주소값을의미하므로, 포인터변수에대입가능 int ar[5]{2, 3, 5, 7, 11}; int *p ar; printf("%p %d\n", p, *p); 001E40B4 2 0x40 p 0xB4 ar 0xB4 0xB8 0xBC 0xC0 0xC4 2 3 5 7 11 [0] [1] [2] [3] [4] 31 2) 배열과포인터 포인터변수에대한증감연산 주소가가리키는변수의크기단위로증가 or 감소 ( 배열과동일 ) int ar[5]{2, 3, 5, 7, 11}; int *p ar; printf("%p %d\n", p+1, *(p+1)); 001E40B8 3 p+0 p+1 p+2 p+3 p+4 0x40 p ar 0xB4 0xB8 0xBC 0xC0 0xC4 0xB4 2 3 5 7 11 [0] [1] [2] [3] [4] 32
2) 배열과포인터 포인터를배열처럼사용하기 포인터변수도배열의첨자형태로값을참조할수있다. int ar[5]{2, 3, 5, 7, 11}; int *p ar; printf("%p %d %d\n", p, p[0], *p); printf("%p %d %d\n", p+1, p[1], *(p+1)); 001E40B4 2 2 001E40B8 3 3 33 2) 배열과포인터 ( 실습 5) 포인터변수를선언하고, 다음을출력하라. char car[5]{'h','e','l','l','o'}, *cpcar; double dar[5]{1.1, 2.2, 3.3, 4.4, 5.5}, *dpdar; cp, cp[0], *cp cp+1, cp[1], *(cp+1) cp+2, cp[2], *(cp+2) dp, dp[0], *dp dp+1, dp[1], *(dp+1) dp+2, dp[2], *(dp+2) 주소값의변화량을주의해서살펴보자 34
2) 배열과포인터 배열과포인터정리 배열과포인터는동일한형태로사용가능하다 int ar[5], *p ar; 주소 0x40 p 0xB4 ar ar+0 ar+1 ar+2 ar+3 ar+4 p+0 p+1 p+2 p+3 p+4 0xB4 0xB8 0xBC 0xC0 0xC4 2 3 5 7 11 정수 ar[0] ar[1] ar[2] ar[3] ar[4] p[0] p[1] p[2] p[3] p[4] *(ar+0) *(ar+1) *(ar+2) *(ar+3) *(ar+4) *(p+0) *(p+1) *(p+2) *(p+3) *(p+4) 배열형태참조 포인터형태참조 35 2) 배열과포인터 배열과포인터정리 ( 복잡해보이지만다음두가지만기억하자 ) int ar[5], *p ar; 주소값에 1 을더하면, 변수의크기만큼주소값이증가한다. ar + 3, p + 3 : ar 과 p 모두주소값을의미함 주소값이주어졌을때, 해당주소에저장된원소 ( 변수 ) 값은다음두가지형태로참조할수있다. arr[3] 과 p[3] : 배열첨자형태 *(arr+3) 과 *(p+3) : 포인터참조형태 배열이름이든포인터변수이든주소값을의미하고, 따라서참조방식도동일하다. 36
2) 배열과포인터 배열과포인터주의사항 1 그러면, 배열이름은포인터변수와완전히동일한가? 배열이름은포인터상수 ( 바꾸지못하는수 ) 이다 대입문의왼쪽에서사용될때 (l-value), 차이발생 int ar[5], *p, num; p # // 가능 ar # // 불가능 ( 컴파일에러 ) 변수가선언되면메모리에그변수를위한공간이할당되는데, 할당된공간의위치는바뀌지않는다. 이는배열도마찬가지.. 37 2) 배열과포인터 배열과포인터주의사항 2 포인터를배열의중간원소에연결시킬수도있다. int ar[5]{2, 3, 5, 7, 11}; int *p &ar[2]; // 2번원소에연결 printf("%p %d %d\n", ar, ar[0], *ar); printf("%p %d %d\n", p, p[0], *p); 0x40 p 0xBC 001E40B4 2 2 0xB4 0xB8 0xBC 0xC0 0xC4 001E40BC 5 5 ar 2 3 5 7 11 [0] [1] [2] [3] [4] 단지포인터는자신이가리키는주소를기준으로배열처럼쓰는것뿐임 38
2) 배열과포인터 배열과포인터주의사항 3 포인터의참조연산자사용시괄호에유의 *(ar+2) ar[2] 5 *ar + 2 *(ar) + 2 ar[0]+2 4 ( 연산자우선순위때문 ) int ar[5]{2, 3, 5, 7, 11}; int *p ar; printf("%d %d\n", *(ar+2), *ar+2); printf("%d %d\n", *(p+4), *p+4); 5 4 11 6 39 3) 포인터연산 주소값에정수를더하거나빼기 ++, --, +, - 와같이덧셈, 뺄셈에대한연산자는모두가능 int ar[5]{2, 3, 5, 7, 11}, i4; int *p &ar[1]; printf("%p %d\n", p, *p); // &ar[1], ar[1] printf("%p %d\n", p, *p); // ar[0] printf("%p %d\n", p+i, *(p+i)); //ar[4](p의값 )+16 0018F9A8 3 0018F9A4 2 0018F9B4 11 앞서본바와같이주소가가리키는자료형의크기단위로증감됨 40
3) 포인터연산 주소값비교하기,!, <, >, >, < 연산자사용가능 int ar[5]{2, 3, 5, 7, 11}, *p1, *p2; p1 &ar[1]; p2 &ar[4]; printf("%p %p\n", p1, p2); printf("%d %d\n", p1 < p2, *p1 < *p2); 0018F9DC 0018F9E8 1 0 포인터연산은일반변수에연결됐을때도사용할수있지만, 일반적으로배열에연결됐을때의미가있다. 41 3) 포인터연산 포인터연산주의사항 더하기, 빼기연산만가능 ( 사칙연산중 ) 곱하기, 나머지, 나머지연산은허용되지않음 연산에정수만사용가능 실수는물론주소값끼리더하거나빼기도허용되지않음 int num1, num2; printf("%p", &num1 * 2); // 컴파일오류 printf("%p", &num1 + &num2); // 컴파일오류 42
3) 포인터연산 포인터연산의활용 : 포인터연산을이용한배열훑어보기 int ar[10]{2, 3, 5, 7, 11, }; int *p; for( p ar ; p < &ar[10] ; ++p ) printf("%d ", *p); [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] ar 2 3 5 7 2 3 5 7 7 11 for 문초기화 p ++p for 문종료조건 43 4) 함수와포인터 함수의매개변수로포인터변수사용 매개변수를포인터로선언 void ifunc(int *ip){ *ip + 1; } void main(){ int x 10; ifunc(&x); printf( %d, x); } 매개변수로주소값사용 실행결과 11 44
4) 함수와포인터 두변수를바꾸는 swap 함수만들기 (call-by-value) void swap(int x, int y){ int temp; temp x; x y; y temp; } void main(){ int x 10, y 20; swap(x, y); printf( %d %d, x, y); } 실행결과 10 20 45 4) 함수와포인터 두변수를바꾸는 swap 함수만들기 (call-by-reference) void swap(int *px, int *py){ int temp; temp *px; *px *py; *py temp; } void main(){ int x 10, y 20; swap(&x, &y); printf( %d %d, x, y); } 실행결과 20 10 46
4) 함수와포인터 매개변수로포인터와배열 void func(int *arr) { } void func(int arr[]) { } 두함수는같은함수 배열과포인터중의미를두고자하는것으로선언 47 5) *void 포인터 자료형이정해지지않은포인터 모든자료형을가리킬수있음 void 포인터선언 void * p; int i; float f; p &i; p &f; 48
5) *void 포인터 void 포인터는연산과값변경을할수없음 변수의값에접근하기위해선형변환필요 void * p; int I 7; float f 23.5; p &i; printf( i %d n, *((int *)p)); p &f; printf( f %f n, *((float *)p)); 실행결과 7 23.5 49 6) * 함수포인터 함수의주소를가리키는포인터 함수포인터의선언 void func(int n){ } void (*pfunc)(int); pfunc func; 함수포인터선언 50
6) * 함수포인터 함수포인터의예제 void func(void){ printf( func 함수가실행되었습니다. n ); } void main(){ int (*pfunc)(void); pfunc func; func(); pfunc(); } 실행결과 func 함수가실행되었습니다. func 함수가실행되었습니다. 51