반응형
※ 본 자료는 위 책을 읽고 개인적으로 정리한 포스트임을 알려드립니다.
# 포인터와 함수
- 포인터는 함수로 데이터를 전달하거나 함수에 의해 데이터를 수정할 수 있게함.
- 복잡한 데이터 역시 구조체의 포인터 형태로 함수에 전달되거나 반환될 수 있음.
- 포인터가 데이터 타입이 아닌 함수의 주소를 가리키는 경우
- 프로그램의 실행 흐름을 동적으로 제어하는 데 사용될 수 있음.
- 프로그램 스택 이해
- 현대의 블록 구조 언어에서 함수의 실행을 지원하기 위해 사용
- 함수가 호출되면 함수의 스택 프레임이 생성되고 프로그램 스택에 추가(push)
- 함수가 반환될 때
- 프로그램 스택은 C언어와 같은 현대의 블록 구조 언어에서 함수의 실행을 지원하기 위해 사용
- 함수는 함수에 의해 참조되는 데이터를 수정할 수 있음.
- 덩치가 큰 데이터를 효과적으로 전달
# 프로그램 스택과 힙
- 프로그램 스택과 힙은 런타임을 구성하는 중요한 요소들
- 프로그램 스택
- 프로그램 스택은 함수의 실행을 지원하기 위한 메모리 영역
- 공유된 메모리 영역에서 프로그램 스택은 메모리의 낮은 부분을 사용하는 경향
- 힙은 메모리의 높은 부분을 사용
- 프로그램 스택은 스택 프레임을 포함
- 활성화 레코드(Activation Records) or 활성화 프레임(Activation Frames)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void function2() { Object *var1 = ...; int var2; ` void function1(){ Object *var3 = ...; function2(); } int main(){ int var4; function1(); } |
- 입력된 스택은 위쪽으로 자람
- 함수의 스택 프레임이 프로그램 스택에서 제거(pop)
- 스택 프레임에 의해 사용된 메모리는 초기화되지 않음
- 힙은 할당과 해제의 반복으로 단편화 이슈 발생
# 스택 프레임의 구성
- 구성 단계
- 반환 주소 : 함수 종료 후 돌아가야 할 프로그램 내의 주소
- 로컬 변수 저장소 : 로컬 변수를 위해 할당된 메모리
- 매개변수 저장소 : 함수의 매개변수를 위해 할당된 메모리
- 스택 포인터와 기반 포인터 (base pointer) : 스택 관리 목적으로 런타임 시스템에 의해 사용되는 포인터
- 발생 문제
- 스택 프레임이 프로그램 스택에 입력될 때, 메모리 부족 상태가 발생할 수 있다.
- 이러한 상태를 스택 오버플로(stack overflow)라고 하며 일반적으로 프로그램의 비정상 종료를 초래
- 메모리의 같은 개체에 접근하는 경우 잠재적인 충돌로 이어질 수 있음.
# 포인터에 의한 전달과 반환
- 포인터를 함수로 전달하면 해당 개체를 전역으로 만들지 않고도 다양한 함수에서 참조가 가능해짐.
- 단지 해당 메모리 개체에 접근이 필요한 함수들만 접근이 가능해지며 해당 개체를 복사하지 않아도됨
- 데이터를 포인터로 전달하면서 상수 포인터를 전달하면 함수내에서 데이터가 수정되는 것을 방지할 수 있음.
- "상수 포인터 전달하기"절
- 함수 내에서 수정해야 할 데이터가 포인터인 경우, 인자로 포인터의 포인터를 전달
- by value 전달 문제
- 아주 큰 구조체를 통째로 함수로 전달하면 해당 구조체의 모든 바이트를 복사해야 하며, 이 때문에 프로그램은 느려지고 더 많은 스택 프레임 메모리를 사용
- 포인터로 전달
- 데이터를 포인터로 전달하는 이유는 함수 내에서 전달된 데이터를 수정하기 위해서
- 매개변수에 의해 참조되는 값을 상호 교환하는 스왑(swap) 기능 구현
1 2 3 4 5 6 7 8 9 10 11 12 13 | void swapWithPointers(int* pnum1, int* pnum2){ int tmp; tmp = *pnum1; *pnum1 = *pnum2; *pnum2 = tmp; } int main(){ int n1 = 5; int n2 = 10; swapWithPointers(&n1, &n2); return 0; } |
- pnum1과 pnum2이 스왑 동작이 처리되는 동안 역참조됨
- 결과
- n1과 n2값이 수정이됨
# 값에 의한 전달
- 복사된 값이 num1과 num2에 저장될 뿐이며 num1을 수정해도 인자 n1은 변경되지 않는다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | void swap(int num1, int num2){ int tmp; tmp = num1; num1 = num2; num2 = tmp; } int main(){ int n1 = 5; int n2 = 10; swap(n1, n2); return 0; } |
# 상수 포인터 전달하기
- 구조체와 같은 큰데이터를 함수로 전달할 때 많은 메모리의 복사를 피하고 데이터의 주소만 전달하는 효과적인 방법
- 단순히 포인터만 전달하면 전달된 데이터가 수정될 수 있으므로, 데이터의 수정이 불가하게 상수 포인터로 전달 한다.
1 2 3 4 5 6 7 8 9 | void passingAddressOfConstants(const int* num1, int* num2){ *num2 = *num1; } int main(){ const int limit = 100; int result = 5; passingAddressOfConstants(&limit, &result); } |
# 포인터 반환하기
- 적절한 데이터 타입의 포인터를 반환하도록 선언
- 함수로부터 메모리 개체를 반환할 필요가 있는 경우
- 함수 내에서 malloc으로 메모리를 할당한 후 함수 종료 시 반환, 이 함수를 호출한 호출자(caller)는 반환된 메모리를 해제할 책임 있다.
- 수정할 메모리 개체를 함수의 인자로 전달, 이 방법은 메모리 개체의 할당과 해제에 대해서 호출에게 책임이 있다.
# malloc 함수를 사용하여 반환될 메모리를 할당
- 그리고 메모리를 반환
1 2 3 4 5 6 7 8 9 10 11 12 | int* allocateArray(int size, int value){ int* arr = (int*)malloc(size * sizeof(int)); for(int i=0; i<size; i++){ arr[i] = value; } return arr; } int* vector = allocateArray(5, 45); for(int i=0; i<5; i++){ printf("%d\n", vector[i]); } |
- 포인터를 반환 받는 코드
1 2 3 4 | int* vector = allocateArray(5, 45); for(int i=0; i<5; i++){ printf("%d\n", vector[i]); } |
- 초기화되지 않은 포인터의 반환
- 잘못된 주소를 가리키는 포인터의 반환
- 로컬 변수를 가리키는 포인터의 반환
- 반환된 포인터의 메모리 해제 실패
# 로컬 데이터 포인터
- 프로그램 스택의 동작 방식을 이해하지 못할 경우
- 로컬 데이터에 대한 포인터를 반환하는 실수를 하기 쉽다.
- 로컬 데이터의 함수는 종료 즉시 스택 프레임이 스택에서 제거됨.
- arr을 static으로 선언
- 변수의 범위 함수로 제한
# Null 포인터 전달
1 2 3 4 5 6 7 8 9 10 11 | int* allocateArray(int *arr, int size, int value){ if(arr != NUULL){ for(int i=0; i<size; i++){ arr[i] = value; } } return arr; } int *vector = (int*)malloc(5 * sizeof(int)); allocateArray(vector, 5, 45); |
#포인터의 포인터 전달
- 포인터 자체의 수정을 원하면 포인터의 포인터를 전달해야 한다.
- 해당 포인터는 함수 내에서 메모리가 할당되고 초기화함
# 사용자 정의 free 함수 작성
- free함수는 전달된 포인터가 NULL인지 검사하지 않으며, 할당 해제 후 반환 시에 포인터를 NULL로 설정하지도 않는다.
- 메모리 해제 후 포인터를 NULL로 설정하는 것은 매우 좋은 습관
- 예시
1 2 3 4 5 6 | void safeFree(void **pp){ if (pp != NULL && *pp != NULL) { free(*pp); *pp = NULL; } } |
# 함수 포인터
- 함수 포인터는 함수의 주소를 가리키는 포인터
- 함수 포인터는 어떠한 조건 문장도 사용하지 않고서 컴파일 시간에 미리 결정된 순서가 아닌 함수의 실행을 제어하는 방법
- 분기 예측(prediction) : 프로세서가 다음에 실행될 것으로 판단되는 분기 처리를 시작하게 되며, 프로세서가 성공적으로 정확한 분기를 예측한다면 현재 파이프라인에 있는 명령을 폐기할 필요가 없어 성능이 향상됨
- 선언
- void (*foo)();
- int (*f1)(double);
- void (*f2)(char*);
- double* (*f3)(int, int);
- 사용
- 123456789101112int (*fptr1)(int);int square(int num) {return num*num;}int n = 5;fptr1 = square;printf("%d squared is %d\n", n, fptr1(n));fptr1 = □ // 주소 연산자의 사용은 중복된 연산으로 사용할 필요가 없다.
- 타입 정의 사용
1 2 3 4 5 6 | typedef int (*funcptr)(int); ... funcptr fptr2; fptr2 = square; printf("%d squared is %d\n", n, fptr(n)); |
- 함수 포인터 전달
- 함수 포인터의 선언 > 함수의 매개변수로 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | int add(int num1, int num2){ return num1 + num2; } int sub(int num1, int num2){ return num1 - num2; } typedef int (*fptrOperation)(int, int); int compute(fptrOperation operation, int num1, int num2){ return operation(num1, num2); } printf("%d\n", compute(add, 5, 6)); printf("%d\n", compute(sub, 5, 6)); |
- 함수 포인터 반환
- 함수 선언 시 함수의 포인터를 반환하도록 선언
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | int add(int num1, int num2){ return num1 + num2; } int sub(int num1, int num2){ return num1 - num2; } typedef int (*fptrOperation)(int, int); fptrOperation select(char opcode) { switch(opcode){ case '+' : return add; case '-' : return subtract; } } int evaluate(char opcode, int num1, int num2){ fptrOperation operation = select(opcode); return operation(num1, num2); } printf("%d\n", evaluate('+', 5, 6)); printf("%d\n", evaluate('-', 5, 6)); |
- 함수 포인터의 배열 이용
- 어떤 조건에 근거하여 실행할 함수를 선택하는 기능을 구현하는데 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | typedef int (*operation)(int, int); operation operations[128] = {NULL}; // 또는 int (*operations[128])(int, int) = {NULL}; void initializeOperationArray() { operation['+'] = add; operation['-'] = sub; } int evaluateArray(char opcode, int num1, int num2){ fptrOperation operation; operation = operations[opcode]; return operation(num1, num2); } initializeOperationsArray(); printf("%d\n", evaluatedArray('+', 5, 6); printf("%d\n", evaluatedArray('-', 5, 6); |
- 함수 포인터 캐스팅
- 하나의 함수를 가리키는 포인터는 다른 타입으로 캐스팅이 가능
- 런타임 시스템은 함수 포인터의 매개변수가 올바른지 확인하지 않기 때문에 함수 포인터의 캐스팅은 신중해야함
- 함수 포인터를 다른 함수 포인터로 캐스팅한 후 다시 원래의 함수 포인터로 되돌리는 것이 가능
- 캐스팅 후에도 포인터는 원래의 포인터와 완전히 같음.
- 사용 코드
- 123456789typedef int (*fptrToSingleInt)(int);typedef int (*fptrToTwoInts)(int, int);int add(int, int);fptrTwoInts fptrFirst = add;fptrToSingleInt fptrSecond = (fptrToSingleInt)fptrFirst;fptrFirst = (fptrToTwoInts)fptrSecond;pritnf("%d\n", fptrFirst(5,6));
- 반환 타입이 다른 함수 포인터로 캐스팅할 수 없다.
반응형
'Programming > C/C++' 카테고리의 다른 글
C/strcmp, wcscmp, _mbscmp 차이 (0) | 2018.05.16 |
---|---|
C/GCC/최적화 및 디버깅 옵션 추가 (0) | 2018.03.14 |
C/Pointer/C포인터의 이해와 활용 - 2 (0) | 2018.03.12 |
C/Pointer/C포인터의 이해와 활용 - 1 (0) | 2018.03.12 |
C/Symbolic Execution/KLEE Reference (0) | 2018.02.06 |