문타쿠, 공부하다.
[독하게 시작하는 C 프로그래밍] 섹션 16. 함수 응용 본문
매개변수 전달 기법
- C언어에서는 참조형을 포인터로 구현
- 인수(상수, 변수), 매개변수, 파라미터, 아규먼트 등은 다 같은 말
- 매개변수는 Stack 영역 사용
Call by value
#include <stdio.h>
int Add(int num1, int num2);
int main(void)
{
int a = 5, b = 10;
printf("5 + 10 = %d\n", Add(a, b));
return 0;
}
int Add(int num1, int num2)
{
return num1 + num2;
}
Call by reference
#include <stdio.h>
int Add(int* num1, int* num2);
int main(void)
{
int a = 5, b = 10;
printf("5 + 10 = %d\n", Add(&a, &b));
return 0;
}
int Add(int* num1, int* num2)
{
return *num1 + *num2;
}
[필수 실습 문제] MyStrcpy() 함수 작성하기
#include <stdio.h>
void MyStrcpy(char* dst, unsigned int size, char* src);
int main(void)
{
char szBufferSrc[12] = { "TestString" };
char szBufferDst[12] = { 0, };
MyStrcpy(szBufferDst, sizeof(szBufferDst), szBufferSrc);
puts(szBufferDst);
return 0;
}
void MyStrcpy(char* dst, unsigned int size, char* src)
{
size_t lenSrc = strlen(src);
for (int i = 0; i < lenSrc; i++)
dst[i] = src[i];
}
Stack frame과 지역변수 주소 반환 문제
- 피호출 함수의 지역변수는 함수의 반환과 함께 모두 소멸
- 소멸된 메모리 영역의 주소를 호출자 함수에게 반환하고 접근하는 것은 매우 심각한 오류
- main() 함수의 포인터 변수 result에는 Testfunc1() 함수가 반환하는 주솟값을 담는다.
- Testfunc1() 함수는 호출됨과 동시에 스택 영역에 올라오게 되고, Testfunc1() 함수의 지역변수인 data도 스택 영역에 올라오게 된다.
- Testfunc1() 함수는 본인의 지역변수인 data의 주솟값(ex. A번지)을 반환한 후 스택 영역에서 사라진다.
- Testfunc1() 함수가 스택 영역에서 사라진다는 말은 Testfunc1() 함수의 지역변수인 data도 스택 영역에서 사라진다는 소리이다.
- 여기서 중요한 점은, Testfunc1() 함수의 지역변수인 data가 스택 영역에서 사라지면서 data가 가지고 있던 100이라는 값은 사라지지 않은 채 반환된 주솟값(A번지) 자리에 쓰레기 값으로써 남아있게 된다.
- 그래서 main() 함수의 printf() 함수의 출력 결과(*result = A번지의 값)로 나온 100이라는 값은 정상적으로 출력된 100이 아닌 쓰레기 값으로써의 100이다.
쓰레기 값임을 증명하기 위해 Testfunc2() 함수 추가
- 위 설명의 1. ~ 5.까지 동일
- Testfunc2() 함수가 두 번째로 호출되면서, Testfunc2() 함수의 지역변수인 data2도 스택 영역에 올라오게 된다.
- 이때, Testfunc2() 함수는 Testfunc1() 함수와 동일한 반환형(int*)을 가지고 있어 Testfunc1() 함수가 있었던 스택 영역 자리를 물려받게 된다.
- 자리를 물려받게 되면서 Testfunc2() 함수의 지역변수인 data2 역시 Testfunc1() 함수의 지역변수인 data가 있었던 자리(A번지)를 물려받게 되고, 그 자리에 있던 쓰레기 값 100은 data2의 값 1로 덮어쓰우지게 된다.
- Testfunc2() 함수는 NULL을 반환하면서 본인의 지역변수 data2와 함께 스택 영역에서 사라지게 되고, data2가 있던 자리(A번지)에는 쓰레기 값으로써의 1이 남게 된다.
- 그래서 main() 함수의 printf() 함수의 출력 결과(*result = A번지의 값)로 쓰레기 값 1이 나오게 된다.
Call by reference와 메모리 동적 할당 이슈
Call by reference
#include <stdio.h>
void Swap(int* p1, int* p2)
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
int main(void)
{
int a = 10, b = 20;
printf("Swap 함수 호출 전\n");
printf("x = %d, y = %d\n\n", a, b);
Swap(&a, &b);
printf("Swap 함수 호출 후\n");
printf("x = %d, y = %d\n", a, b);
return 0;
}
메모리 동적 할당 및 해제와 함수
#include <stdio.h>
#include <stdlib.h>
char* GetName(void)
{
char* name = (char*)calloc(32, sizeof(char));
printf("君の名前は?: ");
gets_s(name, sizeof(char) * 32);
return name;
}
int main(void)
{
char* name = GetName();
printf("당신의 이름은 %s입니다.\n", name);
free(name);
return 0;
}
- Callee(GetName())가 메모리를 동적 할당한 후 반환하는 구조는 문제의 여지가 있음
- GetName() 함수는 값을 반환한 후 스택에서 사라진다.
- 여기서 중요한 포인트는 GetName() 함수는 스택에서 사라진 상태인데, 아직 GetName() 함수에서 할당한 메모리를 해제하지 않았다는 점이다.
- GetName() 함수는 이미 스택에서 사라져버렸기 때문에 GetName() 함수에서 할당한 메모리의 해제는 Caller인 main() 함수가 해야 한다. => 메모리 할당과 해제가 분리돼 버림
- (메모리 할당과 해제가 분리돼버렸기 때문에) 메모리 해제에 대한 확실한 안내 필요 -> 철저하게 문서로 작성
- (포인터만 봐서는 메모리의 크기를 알 수 없으므로) 할당된 메모리 크기 전달문제 고려
재귀호출
- 함수 코드 내부에서 다시 자신을 호출하는 것 (즉, Caller == Callee)
- 반복문과 Stack 자료구조를 합친 것
- 비선형 자료구조에서 매우 중요하게 활용
- 함수 호출 오버헤드는 감수
- 논리 오류 발생 시 Stack overflow 발생
*참고
https://youtu.be/4YQMVmjgx98?si=byFJnrajkFhVIQdS
문자열 처리 - 주요 함수 소개 및 strcat() 함수 성능 개선
문자 처리 함수
- isalpha(), isdigit(), isxdigit(), isalnum(), islower(), isupper(), isspace(), toupper(), tolower()
- gets(), gets_s(), puts()
- sprintf(), printf(), scanf_s()
- strcpy(), strcat(), strstr()
- strpbrk(), strtok()
- 등등 많음 -> 이제는 시대가 변했다 -> ChatGPT를 잘 활용할 것!
가변 길이 입력에 의한 Stack frame 손상
#include <stdio.h>
void GetString(void)
{
char szBuffer[8] = { 0 };
int nData = 0x11223344;
// 보안 결함이 있는 gets로 인하여 다른 데이터의 스택 영역이 손상당함
// 사용자의 입력에 대한 검증은 필수
gets(szBuffer);
printf("%s, %08X\n", szBuffer, nData);
return;
}
int main(void)
{
GetString();
return 0;
}
유틸리티 함수 - atoi(), atol(), atof()
#include <stdio.h>
int main(void)
{
// atoi(): 문자열을 정수로 변환
char* s1 = "1004";
printf("%d\n", atoi(s1));
return 0;
}
유틸리티 함수 - time(), localtime(), ctime()
S/W를 개발하다 보면 기본적으로 로그(log) 기록을 남겨야 할 때가 많다.
로그를 남기려면 '어떤 이벤트가 있었습니다~' 하고 해당 이벤트가 발생한 시간을 기술해야 한다. -> 이 시간을 기술하지 않는다면 log로 남겨봤지 의미가 음슴
그래서 시간을 다룰 일이 상당히 많은데, 이때 사용하는 것이 바로 time() 함수이다.
time() 함수 사용 시 time.h 헤더 파일을 추가해야하며, 반환하는 자료형은 time_t로 64bit 부호 없는 정수 즉, unsigned int라고 볼 수 있다.
예제는 가볍게 보고 넘어가기.
유틸리티 함수 - srand(), rand()
개발을 하다 보면 임의 값을 추출해야 하는 경우가 많다. (ex) 가위바위보, 로또 번호 추첨 등등...)
이때 사용하는 것이 바로 rand() 함수
rand() 함수를 사용하기 전 srand()와 time() 함수를 사용하여 초기화 작업을 해주어야 한다.
초기화 작업을 하지 않으면 여러 번 출력할 때마다 같은 값이 출력되기 때문!
#include <stdio.h>
#include <time.h>
int main(void)
{
int i = 0;
// 초기화 작업 필수
srand((unsigned)time(NULL));
for (i = 0; i < 5; i++)
printf("%6d\n", rand());
printf("\n");
for (i = 0; i < 5; i++)
printf("%6d\n", rand() % 10);
return 0;
}
유틸리티 함수 - system(), exit()
시스템에게 명령을 내리는 system() 함수
stdlib.h 헤더 파일을 추가해야 사용 가능
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char input[512] = { 0 };
printf("Input Command>> ");
gets_s(input, sizeof(input));
system(input);
return 0;
}
notepad.exe를 입력 후 엔터 -> 메모장 프로그램 실행
화면을 지우는 cls를 입력 후 엔터 -> 화면에 있던 내용들이 지워짐
값을 반환시키는 것 없이 프로그램을 즉시 종료시키는 exit() 함수
stdlib.h 헤더 파일을 추가해야 사용 가능
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char ch;
printf("프로그램을 종료하시겠습니까? (Y or N)\n");
printf(">> ");
ch = getchar();
if (ch == 'y' || ch == 'Y')
{
puts("프로그램을 종료합니다.");
exit(1);
}
puts("프로그램을 종료하지 않습니다.");
return 0;
}
'C언어 > 독하게 시작하는 C 프로그래밍' 카테고리의 다른 글
[독하게 시작하는 C 프로그래밍] 섹션 18. 파일 입/출력 (0) | 2023.11.30 |
---|---|
[독하게 시작하는 C 프로그래밍] 섹션 17. 구조체와 공용체 (0) | 2023.11.26 |
[독하게 시작하는 C 프로그래밍] 섹션 15. 메모리와 포인터 (0) | 2023.11.19 |
[독하게 시작하는 C 프로그래밍] 섹션 14. 함수에 대한 기본 이론 (0) | 2023.11.12 |
[독하게 시작하는 C 프로그래밍] 섹션 13. 배열과 프로그래밍 기법 (Part 3. C언어 수준향상) (0) | 2023.11.09 |