Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
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
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

문타쿠, 공부하다.

[독하게 시작하는 C 프로그래밍] 섹션 16. 함수 응용 본문

C언어/독하게 시작하는 C 프로그래밍

[독하게 시작하는 C 프로그래밍] 섹션 16. 함수 응용

개발새발 문타쿠 2023. 11. 26. 16:49

매개변수 전달 기법

  • 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과 지역변수 주소 반환 문제

  • 피호출 함수의 지역변수는 함수의 반환과 함께 모두 소멸
  • 소멸된 메모리 영역의 주소를 호출자 함수에게 반환하고 접근하는 것은 매우 심각한 오류

  1. main() 함수의 포인터 변수 result에는 Testfunc1() 함수가 반환하는 주솟값을 담는다.
  2. Testfunc1() 함수는 호출됨과 동시에 스택 영역에 올라오게 되고, Testfunc1() 함수의 지역변수인 data도 스택 영역에 올라오게 된다.
  3. Testfunc1() 함수는 본인의 지역변수인 data의 주솟값(ex. A번지)을 반환한 후 스택 영역에서 사라진다.
  4. Testfunc1() 함수가 스택 영역에서 사라진다는 말은 Testfunc1() 함수의 지역변수인 data도 스택 영역에서 사라진다는 소리이다.
  5. 여기서 중요한 점은, Testfunc1() 함수의 지역변수인 data가 스택 영역에서 사라지면서 data가 가지고 있던 100이라는 값은 사라지지 않은 채 반환된 주솟값(A번지) 자리에 쓰레기 값으로써 남아있게 된다.
  6. 그래서 main() 함수의 printf() 함수의 출력 결과(*result = A번지의 값)로 나온 100이라는 값은 정상적으로 출력된 100이 아닌 쓰레기 값으로써의 100이다.

쓰레기 값임을 증명하기 위해 Testfunc2() 함수 추가

  1. 위 설명의 1. ~ 5.까지 동일
  2. Testfunc2() 함수가 두 번째로 호출되면서, Testfunc2() 함수의 지역변수인 data2도 스택 영역에 올라오게 된다.
  3. 이때, Testfunc2() 함수는 Testfunc1() 함수와 동일한 반환형(int*)을 가지고 있어 Testfunc1() 함수가 있었던 스택 영역 자리를 물려받게 된다.
  4. 자리를 물려받게 되면서 Testfunc2() 함수의 지역변수인 data2 역시 Testfunc1() 함수의 지역변수인 data가 있었던 자리(A번지)를 물려받게 되고, 그 자리에 있던 쓰레기 값 100은 data2의 값 1로 덮어쓰우지게 된다.
  5. Testfunc2() 함수는 NULL을 반환하면서 본인의 지역변수 data2와 함께 스택 영역에서 사라지게 되고, data2가 있던 자리(A번지)에는 쓰레기 값으로써의 1이 남게 된다.
  6. 그래서 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;
}