C 언어와 포인터 (1)
포인터의 사용법
"완전학습을 지향하는" NHN NEXT 정호영
나눔고딕 및 나눔고딕코딩 글꼴을 설치해 주세요 .
포인터의 용도
1. 원하는 메모리 번지에 직접 값을 쓰기2. 변수의 동적 할당3. Call by Reference4. 배열과 문자열의 조작5. 구조체의 효율적 사용
등등…
0. void 포인터 이야기
void * 는 무엇이든 담을 수 있는 그릇입니다 .
void *ptr;int a = 10;double d = 3.14;ptr = &a; //okptr = &d; //ok
void 포인터에 * 연산자를 사용하면 어떻게 될까요 ?
void *ptr;double d = 3.14;ptr = &d; //okprintf("%f\n", *ptr);
void 포인터는 포인티의 타입이 정해지지 않았기 때문에 읽을 수 없습니다 .
그럼 어떻게void 포인터의 포인티 값을 읽을까요 ?
정답은 캐스팅 ( 형변환 ) 입니다 .
1. void 포인터를 포인티의 포인터 타입으로 형변환
2. * 연산자를 붙여서 값을 읽는다 .
예제
(double *) ptr
void *ptr;double d = 3.14;ptr = &d; //okprintf("%f\n", *((double *)ptr)); //ok
*((double *) ptr)
불편한 void* 를 왜 사용하는지는 조금 뒤에…
1. 메모리에 직접 값을 쓰기
메모리의 원하는 번지에 직접 값을 쓰는 경우가 있습니다 . 하드웨어를 직접 제어하는 경우 , 많이 사용합니다 .
예 ) 0x378 번지의 값이 1 이면 USB 가 살아있다 .
char *alive = (char *) 0x378;if (*alive == 1) { //USB 가 살아 있구나 . 뭘 하지 ?
} else { //USB 가 안 살아있음 , 다른 일을 하자 .}
2. 동적할당
malloccallocfree
지난 시간에 우리 마음대로 사용할 수 있는 heap 이라고 하는 공간이 있다고 했습니다 .
이 공간을 배정받는 함수
malloc()calloc()
반납하는 함수 free()
void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t n, size_t size);
우리가 요청한 크기만큼 힙에 공간을 할당해 줍니다 .리턴값은 요청한 공간의 시작주소입니다 .
free 함수는 할당한 메모리를 해제할 때 사용합니다 .할당하고 해제하지 않으면 leak 이 발생할 수 있습니다 .
예제 ) int 크기만큼 공간을 할당받고 싶어요 .
//NAVER cast void * to int * in this case!int *ptr = malloc(sizeof(int));*ptr = 50;printf("%d\n", *ptr);free(ptr);
예제 ) int 3 개만큼 동적 할당을 하고 싶어요 .
* 포인터의 덧셈이 잠깐 나왔습니다 .
int *ptr = malloc(sizeof(int)*3);int *temp;*ptr = 10;temp = ptr + 1;*temp = 20;printf("%d\n", *ptr); //10printf("%d\n", *(ptr + 1)); //?
예제 ) calloc 을 사용해 봅시다 .
* 포인터의 덧셈이 잠깐 나왔습니다 .
int *ptr = calloc(3, sizeof(int));int *temp;*ptr = 10;temp = ptr + 1;*temp = 20;printf("%d\n", *ptr); printf("%d\n", *(ptr + 1));
3. Call by Reference
C 언어의 먼 선조인 포트란은함수에 인자를 전달할 때 원본을 그대로 전달했습니다 .
아래 언어가 포트란이라면…void foo(int i) { i = i + 10; }
main() { a = 100; foo(a); printf("%d\n", a);}
// 포트란이라면 ( 오해하지 마세요 )// 200// C 니까100
조금 더 자세한 설명을 위해 용어를 정의합니다 .
argument: 실제 호출하는 쪽에서는 넘겨주는 변수parameter: 함수의 정의에 있는 변수
앞쪽에서 parameter: iargument: a
모르는 분이 엄청 많습니다 .
중요한 건 C 언어는
argument 에서 parameter 로 갈 때 복사본을 전달해 준다는 사실입니다 .
이것을 call by value 라고 합니다 .
-- return 값도 마찬가지로 복사가 일어납니다 .
call by value - argument 의 값이 바뀌지 않는다 . - 복사본을 전달한다 .
call by reference - argument 의 값이 바뀐다 . - 원본을 전달한다 .
call by value 는 성능상 문제가 되기도 합니다 .
그런데 C 언어에는 call by reference 가 없습니다 !!뭐라고 ??
Java
- 일반 변수는 call by value- 배열 , 클래스 등 복합 타입 변수는 call by reference- 포인터는 완전히 사라졌다 !
c++
- 선언에 & 를 사용하면 참조 연산자 !- c++ 최대의 실수- 포인터도 여전히 사용할 수 있다 .
프로그래머의 욕심은 끝이 없고 , 같은 실수를 반복한다 .
c# = C++ + C++
기본 골격은 java 와 같지만 내 마음대로 선언도 가능
parameter 에 ref 키워드 사용하면 call by referenceargument 에도 반드시 ref 키워드를 사용해야 함
C 언어에는
call by reference 흉내가 있습니다 .call by address 라고 하는 게 더 맞는 표현입니다 .이 때 포인터를 사용합니다 !
void foo(int *i) { *i = *i + 10;}
int main(void) { int a = 100; foo(&a); printf("%d\n", a); return 0;}
// 실행 결과200
int 변수에 대해 call by reference 를 하기 위해서int * 를 사용했습니다 .
char 변수의 call by reference 를 하려면 ?double 변수의 call by reference 를 하려면 ?
int* 변수의 call by reference 를 하려면 ?
int 변수에 대해 call by reference 를 하기 위해서int * 를 사용했습니다 .
char 변수의 call by reference 를 하려면 ? char *double 변수의 call by referenc 를 하려면 ? double *
int* 변수의 call by reference 를 하려면 ? int **
int myalloc(int *ptr, unsigned int size) 를 구현합니다 .- int 전용 동적할당 함수- 성공하면 size, 실패하면 -1 을 리턴
int myalloc (int *ptr, unsigned int numb) { //implement}
int main(void) { int *ptr = NULL; int ret = myalloc(ptr, 1); if (ret == -1) return 1; *ptr = 5054; printf("%d\n", *ptr); return 0; }
어떻게 구현하든지 앞의 함수는 틀렸습니다 .argument 로 전달한 ptr 은 값이 바뀌지 않기 때문입니다 .int * 를 call by reference 로 호출하려면int ** 를 사용합니다 .
int myalloc (int **ptr, unsigned int numb) { *ptr = malloc(sizeof(int) * numb); if (ptr) return numb; else return -1;}
int main(void) { int *ptr = NULL; int ret = myalloc(&ptr, 1); if (ret == -1) return 1; *ptr = 5054; printf("%d\n", *ptr); return 0; }
이중 포인터의 용법 1
단일 포인터의 call by reference 를 위해
4. 포인터와 배열
C 언어의 배열은 90% 정도 포인터랑 닮았습니다 .포인터에도 배열 연산을 그대로 쓸 수 있습니다 !
int arr[5]; double b[]= {1, 2, 3, 4, 5}; // 자동으로 5 개가 됨
이 코드를 실행해 보고 알아낸 사실을 토의해 봅시다 .
int main(void){ int a[5] = {1,2,3,4,5}; printf("%p\n", a); printf("%p %d\n", &a[0], a[0]); printf("%p %d\n", &a[1], a[1]); return 0;}
놀랍게도 배열이름 변수에는 배열의 첫번째 원소의 주소가 들어갑니다 !
arr = &arr[0]
int 의 주소를 저장한 변수 = int *
포인터를 배열에 대입해서 쓸 수 있을까요 ?
HULL?
int main(void){ int a[5] = {1,2,3,4,5}; int *ptr = a; //not &a, &a[0] 도 OK printf("%p\n", ptr); printf("%p %d\n", ptr, *ptr); printf("%p %d\n", ptr + 1, *(ptr +1)); return 0;}
HULL?
무언가 수상하지요 ?
int main(void){ int a[5] = {1,2,3,4,5}; int *ptr = a; //not &a, &a[0] 도 OK printf("%p\n", ptr); printf("%p %d\n", ptr + 0, *(ptr + 0)); printf("%p %d\n", ptr + 1, *(ptr + 1)); return 0;}
int main(void){ int a[5] = {1,2,3,4,5}; printf("%p\n", a); printf("%p %d\n", &a[0], a[0]); printf("%p %d\n", &a[1], a[1]); return 0;}
HULL?
포인터가 미쳐 날뛰고 있습니다 !!
int main(void) { int a[5] = {1,2,3,4,5}; int *ptr = a; printf("%p\n", ptr); printf("%p %d\n", &ptr[0], ptr[0]); printf("%p %d\n", &ptr[1], ptr[1]); return 0;}
int main(void) { int a[5] = {1,2,3,4,5}; printf("%p\n", a); printf("%p %d\n", &a[0], a[0]); printf("%p %d\n", &a[1], a[1]); return 0;}
사실 a[i] 는 *(a + i) 랑 완전히 똑같습니다 .사람의 편의를 위해 읽기 쉽게 만들어 준 거랍니다 .이런 걸 syntax sugar 라고 합니다 .
a[i] = *(a + i)
* 나중에 다시 나오지만 a->b 도 syntax sugar 입니다 .
다 아는 산수 한 번 해볼까요 ?a[3] = *(a + 3) = *(3 + a) = 3[a]
int main() { int a[5] = {1,2,3,4,5}; for (int i = 0; i < 5; i++) printf("%d\n", i[a]); return 0;}
( 형 ~ 오빠 ~, 이거 어디가 잘못된 건지 좀 가르쳐 주세요 .)
잘못된 건 비뚤어진 너의 마음이다 ㅋㅋ .죄송합니다 .
된다고 절대 사용하면 안 됩니다 .
To be continued…