C 언어

IT 위키

C 언어(C programming language)는 시스템 프로그래밍과 응용 프로그램 개발에 널리 사용되는 범용 프로그래밍 언어이다. 절차적 프로그래밍 언어에 속하며, 하드웨어와 가까운 수준의 제어가 가능하면서도 고급 언어의 구조를 제공한다.

C 언어는 1970년대 초 벨 연구소의 데니스 리치(Dennis Ritchie)가 개발한 프로그래밍 언어이다. 원래는 UNIX 운영체제를 구현하기 위해 사용되었으며, 이후 운영체제, 임베디드 시스템, 컴파일러, 데이터베이스, 네트워크 프로그램 등 다양한 분야에서 활용되었다.

C 언어는 비교적 단순한 문법과 강력한 포인터 기능, 메모리 직접 제어 기능을 제공한다. 이러한 특징 때문에 하드웨어에 가까운 저수준 제어가 필요한 분야에서 중요하게 사용된다.

C 언어의 주요 특징은 다음과 같다.

  • 절차적 프로그래밍 언어
  • 구조적 프로그래밍 지원
  • 컴파일 언어
  • 포인터를 통한 메모리 직접 접근 가능
  • 하드웨어와 가까운 저수준 제어 가능
  • 이식성이 높음
  • 실행 속도가 빠름
  • 표준 라이브러리 제공
  • 운영체제와 시스템 소프트웨어 개발에 적합

절차적 프로그래밍

[편집 | 원본 편집]

C 언어는 절차적 프로그래밍 언어이다. 프로그램을 함수와 명령문의 흐름으로 구성하며, 정해진 순서에 따라 명령을 실행한다.

절차적 프로그래밍에서는 문제를 여러 절차 또는 함수로 나누어 해결한다.

예:

int add(int a, int b) {
    return a + b;
}

위 예시는 두 정수를 더하는 함수를 정의한 것이다.

구조적 프로그래밍

[편집 | 원본 편집]

C 언어는 구조적 프로그래밍을 지원한다. 구조적 프로그래밍은 프로그램을 순차, 선택, 반복 구조로 구성하는 방식이다.

대표적인 제어 구조는 다음과 같다.

  • 순차 구조
  • 선택 구조
  • 반복 구조

순차 구조

[편집 | 원본 편집]

명령문이 작성된 순서대로 실행되는 구조이다.

선택 구조

[편집 | 원본 편집]

조건에 따라 실행 흐름이 달라지는 구조이다.

대표 문법은 다음과 같다.

  • if
  • if-else
  • switch

반복 구조

[편집 | 원본 편집]

특정 조건이 만족되는 동안 명령문을 반복 실행하는 구조이다.

대표 문법은 다음과 같다.

  • for
  • while
  • do-while

기본 구조

[편집 | 원본 편집]

C 프로그램은 일반적으로 `main` 함수에서 실행을 시작한다.

예:

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

위 프로그램은 표준 출력에 문자열을 출력하는 가장 기본적인 C 프로그램이다.

전처리기

[편집 | 원본 편집]

전처리기는 컴파일 전에 소스코드를 처리하는 기능이다.

대표적인 전처리 지시문은 다음과 같다.

지시문 설명
#include 헤더 파일을 포함한다.
#define 매크로를 정의한다.
#ifdef 조건부 컴파일에 사용한다.
#ifndef 매크로가 정의되지 않았을 때 조건부 컴파일한다.

예:

#include <stdio.h>
#define MAX_SIZE 100

자료형

[편집 | 원본 편집]

C 언어는 다양한 기본 자료형을 제공한다.

자료형 설명
char 문자형
int 정수형
float 단정도 실수형
double 배정도 실수형
void 값이 없음을 나타냄

자료형은 저장할 데이터의 종류와 메모리 크기를 결정한다.

변수는 데이터를 저장하기 위한 이름이 붙은 메모리 공간이다.

예:

int age = 20;
double score = 95.5;
char grade = 'A';

C 언어에서는 변수를 사용하기 전에 자료형과 함께 선언해야 한다.

상수는 프로그램 실행 중 값이 변경되지 않는 데이터이다.

대표적인 상수 표현은 다음과 같다.

  • 정수 상수
  • 실수 상수
  • 문자 상수
  • 문자열 상수
  • const 변수
  • 매크로 상수

예:

const int MAX = 100;
#define PI 3.14159

연산자

[편집 | 원본 편집]

C 언어는 다양한 연산자를 제공한다.

구분 설명
산술 연산자 +, -, *, /, % 사칙연산과 나머지 연산
관계 연산자 ==, !=, <, >, <=, >= 값 비교
논리 연산자 &&, , ! 논리 조건 처리
대입 연산자 =, +=, -= 값 대입
증감 연산자 ++, -- 값 1 증가 또는 감소
비트 연산자 , ^, ~, <<, >> 비트 단위 연산

제어문

[편집 | 원본 편집]

C 언어의 제어문은 프로그램의 실행 흐름을 제어한다.

조건이 참일 때 특정 문장을 실행한다.

if (score >= 60) {
    printf("pass\n");
}

switch 문

[편집 | 원본 편집]

하나의 값에 따라 여러 경우 중 하나를 선택한다.

switch (grade) {
case 'A':
    printf("excellent\n");
    break;
case 'B':
    printf("good\n");
    break;
default:
    printf("normal\n");
}

반복 횟수가 정해져 있을 때 주로 사용한다.

for (int i = 0; i < 10; i++) {
    printf("%d\n", i);
}

while 문

[편집 | 원본 편집]

조건이 참인 동안 반복한다.

while (count < 10) {
    count++;
}

do-while 문

[편집 | 원본 편집]

반복문 본문을 최소 한 번 실행한 뒤 조건을 검사한다.

do {
    count++;
} while (count < 10);

함수는 특정 작업을 수행하는 코드 블록이다. C 언어에서는 프로그램을 여러 함수로 나누어 작성할 수 있다.

함수는 일반적으로 다음 요소로 구성된다.

  • 반환형
  • 함수명
  • 매개변수
  • 함수 본문
  • 반환값

예:

int add(int a, int b) {
    return a + b;
}

배열은 같은 자료형의 데이터를 연속된 메모리 공간에 저장하는 구조이다.

예:

int scores[5] = {90, 80, 70, 60, 50};

배열의 각 원소는 인덱스를 통해 접근한다.

printf("%d\n", scores[0]);

C 언어의 배열 인덱스는 0부터 시작한다.

문자열

[편집 | 원본 편집]

C 언어에서 문자열은 문자 배열로 표현된다. 문자열의 끝은 널 문자 `\0`으로 표시된다.

예:

char name[] = "Kim";

위 문자열은 내부적으로 다음과 같이 저장된다.

K  i  m  \0

문자열 처리를 위해 표준 라이브러리의 `string.h`를 사용할 수 있다.

대표 함수는 다음과 같다.

  • strlen
  • strcpy
  • strcat
  • strcmp

포인터

[편집 | 원본 편집]

포인터는 메모리 주소를 저장하는 변수이다. C 언어의 핵심 개념 중 하나이며, 배열, 문자열, 동적 메모리, 함수 호출, 자료구조 구현 등에 널리 사용된다.

예:

int a = 10;
int *p = &a;

위 코드에서 `p`는 변수 `a`의 주소를 저장하는 포인터이다.

포인터 연산자

[편집 | 원본 편집]

포인터와 관련된 주요 연산자는 다음과 같다.

연산자 설명
& 변수의 주소를 구한다.
* 포인터가 가리키는 값을 참조한다.

예:

int a = 10;
int *p = &a;

printf("%d\n", *p);

포인터와 배열

[편집 | 원본 편집]

C 언어에서는 배열 이름이 배열의 첫 번째 원소 주소처럼 사용될 수 있다.

예:

int arr[3] = {10, 20, 30};
int *p = arr;

`p`는 배열의 첫 번째 원소를 가리킨다.

포인터 연산을 이용하면 다음과 같이 배열 원소에 접근할 수 있다.

printf("%d\n", *(p + 1));

구조체

[편집 | 원본 편집]

구조체는 서로 다른 자료형의 데이터를 하나로 묶어 표현하는 사용자 정의 자료형이다.

예:

struct Student {
    int id;
    char name[20];
    double score;
};

구조체 변수는 다음과 같이 선언한다.

struct Student s1;

구조체 멤버에는 점 연산자를 사용하여 접근한다.

s1.id = 1;

공용체

[편집 | 원본 편집]

공용체는 여러 멤버가 같은 메모리 공간을 공유하는 사용자 정의 자료형이다.

예:

union Data {
    int i;
    float f;
    char c;
};

공용체는 한 번에 하나의 멤버 값을 저장하는 데 적합하다.

열거형

[편집 | 원본 편집]

열거형은 이름이 붙은 정수 상수 집합을 정의하는 자료형이다.

예:

enum Day {
    MON,
    TUE,
    WED,
    THU,
    FRI
};

열거형은 코드의 가독성을 높이는 데 사용된다.

동적 메모리 할당

[편집 | 원본 편집]

C 언어에서는 실행 중 필요한 만큼 메모리를 할당할 수 있다.

대표 함수는 다음과 같다.

함수 설명
malloc 지정한 크기의 메모리를 할당한다.
calloc 지정한 개수와 크기의 메모리를 0으로 초기화하여 할당한다.
realloc 기존에 할당한 메모리 크기를 변경한다.
free 동적으로 할당한 메모리를 해제한다.

예:

int *p = malloc(sizeof(int) * 10);

if (p != NULL) {
    free(p);
}

동적 메모리를 사용한 뒤에는 반드시 `free`로 해제해야 한다.

파일 입출력

[편집 | 원본 편집]

C 언어는 표준 라이브러리를 통해 파일 입출력을 지원한다.

대표 함수는 다음과 같다.

  • fopen
  • fclose
  • fprintf
  • fscanf
  • fgets
  • fputs
  • fread
  • fwrite

예:

FILE *fp = fopen("data.txt", "r");

if (fp != NULL) {
    fclose(fp);
}

표준 입출력

[편집 | 원본 편집]

C 언어의 표준 입출력은 `stdio.h` 헤더를 통해 제공된다.

대표 함수는 다음과 같다.

함수 설명
printf 형식화된 출력
scanf 형식화된 입력
getchar 문자 입력
putchar 문자 출력
fgets 문자열 입력
puts 문자열 출력

컴파일 과정

[편집 | 원본 편집]

C 언어는 컴파일 언어이다. 소스코드는 컴파일 과정을 거쳐 실행 파일이 된다.

일반적인 컴파일 과정은 다음과 같다.

  1. 전처리
  2. 컴파일
  3. 어셈블
  4. 링크
  5. 실행 파일 생성
단계 설명
전처리 #include, #define 등 전처리 지시문 처리
컴파일 C 소스코드를 어셈블리 코드로 변환
어셈블 어셈블리 코드를 목적 파일로 변환
링크 목적 파일과 라이브러리를 연결하여 실행 파일 생성

gcc 사용 예

[편집 | 원본 편집]

Linux 환경에서는 gcc를 사용하여 C 프로그램을 컴파일할 수 있다.

gcc main.c -o main

실행:

./main

경고 메시지를 더 많이 확인하려면 다음 옵션을 사용할 수 있다.

gcc -Wall -Wextra main.c -o main

헤더 파일

[편집 | 원본 편집]

헤더 파일은 함수 선언, 매크로, 자료형 정의 등을 포함하는 파일이다.

대표적인 표준 헤더는 다음과 같다.

헤더 설명
stdio.h 표준 입출력 함수
stdlib.h 메모리 할당, 변환, 종료 관련 함수
string.h 문자열 처리 함수
math.h 수학 함수
time.h 시간 관련 함수

C 언어와 운영체제

[편집 | 원본 편집]

C 언어는 운영체제 개발에 널리 사용되었다. 특히 UNIX 운영체제는 C 언어로 구현되었으며, 이후 많은 운영체제와 시스템 소프트웨어 개발에 영향을 주었다.

C 언어가 운영체제 개발에 적합한 이유는 다음과 같다.

  • 메모리 직접 제어 가능
  • 포인터 제공
  • 비트 연산 지원
  • 하드웨어 가까운 수준의 제어 가능
  • 실행 속도 우수
  • 컴파일 결과가 효율적임

C 언어와 임베디드 시스템

[편집 | 원본 편집]

C 언어는 임베디드 시스템 개발에서도 널리 사용된다.

이유는 다음과 같다.

  • 제한된 자원에서 효율적인 실행 가능
  • 하드웨어 레지스터 제어 가능
  • 메모리 사용량을 세밀하게 관리 가능
  • 컴파일러 지원 범위가 넓음
  • 실시간 처리에 적합

C 언어의 장점은 다음과 같다.

  • 실행 속도가 빠르다.
  • 하드웨어 제어가 가능하다.
  • 이식성이 높다.
  • 문법이 비교적 단순하다.
  • 운영체제와 시스템 소프트웨어 개발에 적합하다.
  • 다양한 플랫폼에서 컴파일러를 사용할 수 있다.
  • 자료구조와 알고리즘 학습에 적합하다.

C 언어의 단점은 다음과 같다.

  • 메모리 관리를 개발자가 직접 해야 한다.
  • 포인터 사용이 어렵고 오류가 발생하기 쉽다.
  • 배열 경계 검사를 자동으로 수행하지 않는다.
  • 문자열 처리가 불편하다.
  • 메모리 누수와 버퍼 오버플로우 위험이 있다.
  • 객체지향 기능이 기본적으로 제공되지 않는다.
  • 안전성보다 성능과 제어에 초점이 강하다.

주요 오류 유형

[편집 | 원본 편집]

C 언어에서 자주 발생하는 오류는 다음과 같다.

  • 포인터 오류
  • 널 포인터 참조
  • 배열 범위 초과
  • 버퍼 오버플로우
  • 메모리 누수
  • 이중 해제
  • 초기화되지 않은 변수 사용
  • 형 변환 오류
  • 세미콜론 누락
  • 헤더 파일 누락

보안 취약점

[편집 | 원본 편집]

C 언어는 메모리 제어가 강력한 만큼 보안 취약점도 발생하기 쉽다.

대표적인 취약점은 다음과 같다.

  • 버퍼 오버플로우
  • 포맷 스트링 취약점
  • Use-after-free
  • Double free
  • 널 포인터 역참조
  • 정수 오버플로우
  • 메모리 누수

안전한 C 프로그래밍을 위해서는 입력값 검증, 경계 검사, 안전한 문자열 함수 사용, 정적 분석, 코드 리뷰가 중요하다.

C 언어와 C++

[편집 | 원본 편집]

C++는 C 언어를 기반으로 객체지향 프로그래밍, 템플릿, 예외 처리 등 다양한 기능을 확장한 언어이다.

구분 C C++
주요 패러다임 절차적 프로그래밍 객체지향, 제네릭, 절차적 프로그래밍
클래스 없음 있음
함수 오버로딩 없음 있음
예외 처리 기본 제공 안 함 제공
주요 용도 시스템 프로그래밍, 임베디드 시스템, 응용, 게임, 고성능 프로그램

C 언어와 Java

[편집 | 원본 편집]

Java는 C/C++의 영향을 받았지만, 메모리 관리와 실행 방식에서 차이가 있다.

구분 C Java
실행 방식 네이티브 코드로 컴파일 바이트코드와 JVM 기반 실행
메모리 관리 개발자가 직접 관리 가비지 컬렉션 사용
포인터 직접 사용 가능 직접 포인터 없음
플랫폼 독립성 소스 수준 이식성 JVM 기반 이식성
주요 용도 시스템, 임베디드 웹, 서버, 모바일, 기업 애플리케이션

학습 관점

[편집 | 원본 편집]

C 언어는 프로그래밍의 기초뿐 아니라 컴퓨터 구조, 운영체제, 메모리 관리, 자료구조를 이해하는 데 중요한 언어이다.

학습할 때는 다음 항목을 중심으로 정리하는 것이 좋다.

  • 기본 자료형
  • 연산자
  • 제어문
  • 함수
  • 배열
  • 문자열
  • 포인터
  • 구조체
  • 동적 메모리 할당
  • 파일 입출력
  • 컴파일 과정
  • 메모리 오류와 보안 취약점

시험 포인트

[편집 | 원본 편집]
  • C 언어는 절차적 프로그래밍 언어이다.
  • C 언어는 컴파일 언어이다.
  • C 프로그램은 일반적으로 main 함수에서 실행을 시작한다.
  • 포인터는 메모리 주소를 저장하는 변수이다.
  • 배열 인덱스는 0부터 시작한다.
  • 문자열은 널 문자 `\0`으로 끝나는 문자 배열이다.
  • 구조체는 서로 다른 자료형을 하나로 묶는 사용자 정의 자료형이다.
  • malloc, calloc, realloc은 동적 메모리 할당에 사용된다.
  • free는 동적으로 할당한 메모리를 해제한다.
  • C 언어는 배열 경계 검사를 자동으로 수행하지 않는다.
  • 버퍼 오버플로우는 C 언어에서 대표적인 보안 취약점이다.
  • 컴파일 과정은 전처리, 컴파일, 어셈블, 링크 순서로 설명할 수 있다.

같이 보기

[편집 | 원본 편집]