출처: enter.tistory.com/99. Advanced C Programming
01 포인터
포인터
어떠한 값을 저장하는 것이 아닌 어떠한 값의 주소(소위 말하는 메모리의 XX번지)를 저장하는 변수
어떠한 값의 주소는 어떤 값이 저장된 컴퓨터 메모리상의 주소를 뜻한다. 예를 들어 num이라는 변수에 5라는 값이 저장되어 있고 *pnum이라는 포인터 변수에는 num의 주소 123456이 저장된다는 것이다.
num : 5 (주소: 123456)
pnum : 123456
포인터 변수를 만들 때는 변수이름 앞에 *을 붙여주면 그 변수는 포인터 변수가 된다.
(사용할 때 일반변수와의 구분을 위해 주로 ‘*변수이름’의 형식으로 사용한다.)
포인터 변수가 할당되는 공간은 운영체제에 따라 다르다. 이 공간은 포인터 변수의 자료형과는 무관하며,
일반적으로 32비트 운영체제에서는 4바이트, 64비트 운영체제인 경우에는 8바이트를 할당한다.
#include <stdio.h>
int main(void) { int *pnum; //포인터 변수 선언 int num = 12345; //일반변수 선언 & 초기화 int # //num의 주소값을 반환하여 pnum에 저장 printf(”num의 값: %d\n”, num); printf(”num의 변수의 주소: %p\n” &num); printf(“pnum 변수의 값: %p\n” &num); printf(“pnum이 가리키는 값: %d\n” *pnum); } |
num의 값: 123456 num변수의 주소: 0213BA2C pnum변수의 값 : 0213BA2C pnum이 가리키는 값 : 123456 |
실행결과를 보면 pnum의 실질적인 값은 num의 주소를 가리키고 있다. 여기서
*(포인터연산자)는 포인터 변수가 가리키는 곳의 내용을 참조하는 연산자(실행문),
&(주소연산자)는 변수에 할당된 메모리의 시작 주소를 의미하는 연산자이다.
포인터는 가리키고자 하는 변수의 자료형에 따라 포인터변수도 그에 맞춰 동일하게 선언해야 한다.
double num;
double *pnum; //double형 포인터 변수 pnum
char cha;
char *pcha; //char형 포인터 변수 pcha
주소값은 동일한 시스템에서 그 크기가 동일하여 모두 정수의 형태를 띤다. 그렇다면 왜 변수의 자료형으로 포인터 변수를 맞춰주어야 한다는 것일까? 만약 똑같이 int형으로 포인터 변수를 선언했다면 다른 사용자나 자신이 이 포인터 변수가 무엇을 가리키는지 알기 힘들다.
*의 위치는 크게 상관 없다.
int* pnum;
int * pnum;
int *pnum;
#include <stdio.h>
int main(void) { int *pnum; //포인터 변수 pnum 선언 int num=12345; //num값 초기화 pnum=# // pnum에 num의 주소값을 저장 printf(“변경 전 num의 값: %d\n”, num); printf(“변경 전 pnum이 가리키는 값: %d\n”, *pnum); *pnum=54321; //pnum이 가리키는 변수의 값 변경 printf(“변경 후 num의 값: %d\n”, num); printf(“변경 후 pnum이 가리키는 값: %d\n", *pnum); } |
변경 전 num의 값: 12345
변경 전 pnum이 가리키는 값: 12345
변경 후 num의 값: 54321 변경 후 pnum이 가리키는 값: 54321 |
02 NULL 포인터는 무엇인가
NULL 포인터
C언어 표준 문서에는 NULL 포인터를 ‘어떤 것도 가리키지 않는 포인터 값’이라고 정의한다.
포인터 변수의 NULL 초기화
포인터 변수를 NULL로 만들기 위해서는 0으로 지정하면 된다. ASCII 코드에서 0은 NULL로 정의된다.
char *ptr = NULL;
char *ptr = 0;
포인터가 아무것도 가리키지 않는다는 것을 나타날 때 NULL 포인터의 개념은 아주 유용하다.
포인터가 아무것도 가리키지 않는다는 것을 알기 위해서는 NULL(0)과 비교할 수 있다.
NULL 포인터의 사용처
- 포인터 변수 초기화
포인터 변수를 NULL로 초기화하고 프로그램 실행 중 포인터 변수가 임의의 주소를 대입 받았는지 확인하고 그에 따른 행위를 기술할 수 있다.
char *ptr = NULL; … if (ptr == NULL) //가리키는 곳이 없다 else //가리키는 곳이 있다 … |
- 에러를 처리할 때
NULL 포인터는 다음과 같이 에러의 유무를 판단할 때 중요하게 사용된다.
FILE *fp; if ((fp=fopen(“data”, ”r”)) == NULL) { //파일 열기 실패 printf(“fopen()”); exit(1); } //파일 열기 성공
… |
동적메모리 할당
동적메모리 할당을 요청하고 실패 유무를 판단할 때 사용된다.
char *ptr; if ( (ptr=(char*)malloc(100)) == NULL ) { //동적 메모리 할당 실패 printf(“ malloc() ”); exit(1); } //동적 메모리 할당 성공 |
포인터형으로 파생되는 자료형
- 포인터 변수
- 배열 포인터
- 포인터 배열
- 다중 포인터
- 함수 포인터
- void형 포인터
포인터 연산
포인터는 +, - ++, -- 연산자를 이용하여 연산할 수 있으며, 이때 연산은 포인터변수의 자료형 크기에 맞추어 증감한다.
[표] 자료형에 따른 메모리 크기
03 배열과 배열포인터
배열이란 메모리의 연속된 공간에 할당된 동일한 자료형의 데이터 모음.
1차원 배열
다음과 같이 1차원 배열과 포인터 변수가 선언되어 있다.
int num[5] = {10, 20, 30, 40, 50}
int *ptr;
포인터 변수에 배열의 메모리 주소를 할당하기 위한 방법은 다음과 같다.
ptr=num;
배열 변수명은 배열이 할당된 메모리의 시작주소를 의미하므로 이를 포인터 변수에
대입할 수 있다. 즉, 배열 변수의 주소는 배열의 첫 번째 원소의 선두 번지와 같다.
#include <stdio.h> int main(void) { int num[5] = {10,20,30,40,50}; int *ptr;
ptr=num;
printf(“%p\t %p \t, %p \n”, num, &num[0], ptr); return 0; } |
7f7f0868 7f7f0860 7f7f70868 |
** num==&num[0] == ptr은 같은 주소를 갖게 된다.
배열과 포인터 변수와의 관계에서 다음과 같은 표현은 모두 동일한 표현이라는 것을 기억해두자.
num+i = ptr+i = &num[i] = &ptr[i] : 배열의 i+1번째 위치의 주소.
*(num+i) = *(ptr+i) = num[i] = ptr[i] : 배열의 i+1번째 위치의 데이터
#include <stdio.h>
int main() { int num[6] = { 10,20,30,40,50 }, *ptr, tmp; char str[20]=”multi campus”, *p;
printf(“%d, %d \n”, sizeof(num), sizeof(num[0])); printf(“%d, %p, %p \n”, num[0], &num[0], num);
ptr=num;
printf(“%d, %d, %d \n”, *(ptr+0), *(ptr+1), *(ptr+2)); printf(“%d, %d, %d \n”, *(num+0), *(num+1), *(num+2)); printf(“%d, %d, %d \n, num[0], num[1], num[2]”); printf(“%d, %d, %d \n, ptr[0], ptr[1], ptr[2]”);
printf(“%d, %d,m %d \n”, *ptr+0, *ptr+1, *ptr+2);
while(*ptr) printf(“%d”, *ptr++);
printf(“\n\n”);
//포인터의 우선순위 ptr=num; printf(“%p \n”, ptr); tmp=*ptr++; printf(“tmp: %d, ptr: %p, num[0]: %d \n”, tmp, ptr, num[0]);
ptr=num; printf(“%p \n”, ptr); tmp=(*ptr)++; printf(“tmp: %d, ptr: %p, num[0]: %d \n\n“, tmp, ptr, num[0]); printf(“\n”);
p=str; printf(“%p, %p, %s, %c \n”, p, str, str, str[0]);
while(*p) printf(“%c”, p++); prinjtf(“\n”);
return 0; } |
24, 4 10, 7f7f07e0, 7f7f07e0 10, 20, 30 10, 20, 30 10, 20, 30 10, 20, 30 10, 11, 12 10 20, 30, 40, 50
7f7f07e0 tmp: 10, ptr: 7f7f07e4, num[0]: 10 7f7f07e4 tmp: 10, ptr: 7f7f07e4, num[0]: 11
7f7f0800, 7f7f0800, multi campus, m multi campus |
위 예제의 출력 결과를 통해 num[i]와 *(num+i), *(ptr+i)는 같은 내용임을 알 수 있다. 이것은 배열명도 배열의 시작주소를 갖는 포인터 상수이기 때문에 포인터 연산을 통하여 배열에 접근할 수 있다. 또 right to left의 결합성에 따라 괄호를 생략해도 *(ptr++) == *ptr++과 같은 의미가 된다. 포인터 변수는 메로 주소의 값을 변경하거나 증가하는 데 문제가 없다.
그러나, 배열변수명 num++과 같은 포인터 식은 오류이다. 이유는 num은 배열의 시작주소를 갖는 포인터 상수이다. 따라서 num은 그 주소의 값을 변경하거나 새로운 주소를 저장할 수 없다. 따라서 배열명이 그 배열의 시작주소를 갖는다 하더라도 포인터 변수와는 차이가 있음을 이해해야 한다.
'프로그래밍 > C & C++' 카테고리의 다른 글
[C] 날짜/시간 처리 함수 (0) | 2016.07.07 |
---|---|
[C] 문자 처리 함수 (0) | 2016.07.06 |
[C] 구조체 (0) | 2016.03.27 |
[C] 문자열 처리함수 (0) | 2016.03.22 |
[C] 헤더파일 (0) | 2016.01.12 |