Language/C

📌 Chapter 19 - 고급 기법

e-cko 2025. 7. 3. 13:30
반응형
반응형

1. 콜백 함수 (Callback Function)

  • 콜백 함수(callback function)는 다른 함수의 인자로 전달되어 특정 시점에 호출되는 함수이다.
  • 주로 라이브러리 함수나 API가 사용자 정의 동작을 실행할 때 사용된다.
  • 함수 포인터를 사용하여 구현하며, 코드의 유연성과 재사용성을 높인다.
  • 이벤트 처리, 비교 함수, 정렬 함수(qsort )에서 사용된다.
  • : qsort() 함수는 정렬 기준을 결정하는 비교 함수를 인자로 받는다.

⚠️ 함수 포인터를 사용할 때 매개변수 타입과 반환형을 정확히 일치시켜야 한다.
typedef를 사용하면 함수 포인터를 가독성 있게 선언할 수 있다.


💻 예제 코드

#include <stdio.h>

#include <stdlib.h>

 

// 비교 함수 (오름차순 정렬 기준)

int compare(const void* a, const void* b) {

    return (*(int*)a - *(int*)b);

}

 

int main() {

    int arr[] = {5, 2, 9, 1, 3};

    int size = sizeof(arr) / sizeof(arr[0]);

 

    // qsort에 콜백 함수 전달

    qsort(arr, size, sizeof(int), compare);

 

    // 정렬 결과 출력

    for (int i = 0; i < size; i++) {

        printf("%d ", arr[i]);

    }

    return 0;

}

🟰 출력 결과: 1 2 3 5 9


2. 변동 인자 함수 (Variable Argument Function)

  • 인자의 개수가 고정되지 않은 함수이다.
  • 대표적으로 printf() 함수가 해당된다.
  • <stdarg.h> 헤더 파일을 포함해야 한다.
    주요 매크로
    1️
    va_list : 인자 목록을 저장하는 타입이다.
    2️
    va_start() : 가변 인자 목록의 시작을 설정한다.
    3️
    va_arg() : 다음 인자를 지정한 타입으로 꺼낸다.
    4️
    va_end() : 가변 인자 처리를 종료한다.

⚠️ 인자의 개수나 끝을 구분하는 방법을 명확히 지정해야 오류를 방지할 수 있다.
인자 타입을 정확히 지정하지 않으면 정상 작동하지 않을 수 있다.


💻 예제 코드

#include <stdio.h>

#include <stdarg.h>

 

// 가변 인자 함수: 정수 n개를 받아 합을 계산

int sum(int count, ...) {

    va_list args;

    va_start(args, count);

    int total = 0;

 

    for (int i = 0; i < count; i++) {

        total += va_arg(args, int); // 정수 인자 꺼내기

    }

 

    va_end(args);

    return total;

}

 

int main() {

    printf("합계: %d\n", sum(4, 10, 20, 30, 40));

    return 0;

}

🟰 출력 결과: 합계: 100


3. 메모리 관리 심화

1) 메모리 정렬과 패딩

  • 메모리는 CPU가 효율적으로 접근할 수 있도록 정렬(alignment)되어야 한다.
  • 정렬되지 않은 접근은 성능 저하 또는 오류를 유발할 수 있다.
  • 구조체 내부에 패딩(padding)이 삽입되어 정렬을 맞춘다.
    ⚠️ 구조체의 실제 크기는 멤버 크기의 합보다 클 수 있다.

2) 동적 메모리 관리

  • malloc(), calloc(), realloc(), free() 함수의 동작을 정확히 이해한다.
  • 메모리 누수(memory leak), 이중 해제(double free), 미초기화 접근에 주의한다.
    할당 후 포인터의 NULL 여부를 확인해야 안전하다.

💻 예제 코드

#include <stdio.h>

#include <stdlib.h>

 

typedef struct {

    char name[20];

    int age;

    double height;

} Person;

 

int main() {

    // 메모리 할당

    Person* p = (Person*)malloc(sizeof(Person));

 

    if (p == NULL) {

        perror("메모리 할당 실패");

        return 1;

    }

 

    // 구조체 값 설정

    sprintf(p->name, "홍길동");

    p->age = 30;

    p->height = 175.5;

 

    printf("이름: %s, 나이: %d, : %.1f\n", p->name, p->age, p->height);

 

    // 메모리 해제

    free(p);

 

    return 0;

}


4. 오류 처리 및 디버깅 기법

  • C는 오류 발생 시 자동 처리가 없으므로 프로그래머가 직접 관리해야 한다.
  • errno 전역 변수는 에러 번호를 저장한다.
  • perror(), strerror()오류 메시지를 출력하는 함수이다.


대표 방법
1️
assert()를 사용해 조건 검증
2️
gdb와 같은 디버거 사용
3️
printf()로 로그 추적
4️
-Wall, -Wextra 등의 컴파일러 경고 옵션 사용


💻 예제 코드

#include <stdio.h>

#include <stdlib.h>

#include <assert.h>

 

int divide(int a, int b) {

    assert(b != 0); // 0으로 나누지 않도록 검증

    return a / b;

}

 

int main() {

    int result = divide(10, 2);

    printf("결과: %d\n", result);

 

    // divide(10, 0); // 주석 해제 시 assert 실패

 

    return 0;

}


5. 표준 라이브러리 심화

  • string.h: 문자열 처리 함수 (strlen(), strcpy(), strcmp() )
  • stdlib.h: 메모리 할당, 숫자 변환, 정렬 등 (malloc(), atoi(), qsort() )
  • ctype.h: 문자 검사 함수 (isalpha(), isdigit() )

표준 라이브러리를 적극 활용하면 복잡한 로직을 간단하게 구현할 수 있다.


💻 예제 코드

#include <stdio.h>

#include <string.h>

#include <ctype.h>

 

int main() {

    char str[] = "Hello123";

 

    // 문자열 길이 확인

    printf("길이: %lu\n", strlen(str));

 

    // 문자열의 숫자만 출력

    for (int i = 0; i < strlen(str); i++) {

        if (isdigit(str[i])) {

            printf("숫자: %c\n", str[i]);

        }

    }

 

    return 0;

}


6. 멀티스레딩 기초

  • 멀티스레딩(Multithreading)은 하나의 프로그램에서 여러 흐름을 동시에 실행하는 기법이다.
  • POSIX Thread (pthread) 라이브러리를 사용하여 구현한다.
  • 스레드 간 데이터 공유 시 동기화가 필요하다.
    동기화 도구
    1️
    Mutex (뮤텍스)
    2️
    Semaphore (세마포어)

⚠️ 동기화 실패 시 경쟁 상태(race condition) 또는 데드락(deadlock) 문제가 발생한다.


💻 예제 코드

#include <stdio.h>

#include <pthread.h>

 

void* threadFunc(void* arg) {

    printf("스레드 실행 중\n");

    return NULL;

}

 

int main() {

    pthread_t tid;

 

    // 스레드 생성

    pthread_create(&tid, NULL, threadFunc, NULL);

 

    // 메인 스레드는 서브 스레드가 끝날 때까지 대기

    pthread_join(tid, NULL);

 

    printf("메인 종료\n");

    return 0;

}


7. 네트워크 프로그래밍 기초

  • 네트워크 프로그래밍은 두 컴퓨터 간 데이터를 주고받는 기술이다.
  • 소켓 API(Socket API)를 통해 TCP/UDP 통신을 구현한다.
  • 기본 흐름: 소켓 생성바인딩청취연결송수신

반드시 이해해야 할 개념
1️
IP 주소
2️
포트 번호
3️
클라이언트-서버 모델

효율적 처리를 위해 비동기 I/O, 멀티스레딩을 결합하는 것이 일반적이다.


💻 예제 코드 (TCP 클라이언트 예시)

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

 

int main() {

    int sock = socket(AF_INET, SOCK_STREAM, 0); // TCP 소켓 생성

    struct sockaddr_in server_addr;

 

    server_addr.sin_family = AF_INET;

    server_addr.sin_port = htons(8080); // 포트 설정

    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 로컬 호스트

 

    // 서버에 연결 요청

    connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));

 

    char msg[] = "Hello, Server!";

    send(sock, msg, strlen(msg), 0); // 메시지 전송

 

    close(sock); // 소켓 종료

    return 0;

}


마무리 정리
1️
콜백 함수는 함수 포인터로 구현되며, 이벤트 처리 등에 사용된다.
2️
변동 인자 함수는 <stdarg.h>와 매크로로 구현한다.
3️
메모리 정렬과 패딩은 성능 및 안정성과 직결된다.
4️
오류 처리는 errno, perror(), assert() 등을 활용하여 수동으로 관리한다.
5️
표준 라이브러리를 익히면 생산성과 코드 품질이 향상된다.
6️
멀티스레딩은 pthread로 구현하며, 동기화 기법이 필수이다.
7️
네트워크 프로그래밍은 소켓 기반으로 동작하며, 클라이언트-서버 모델을 따른다.

 

반응형