C 언어
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 (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 (count < 10) {
count++;
}
반복문 본문을 최소 한 번 실행한 뒤 조건을 검사한다.
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 언어는 컴파일 언어이다. 소스코드는 컴파일 과정을 거쳐 실행 파일이 된다.
일반적인 컴파일 과정은 다음과 같다.
- 전처리
- 컴파일
- 어셈블
- 링크
- 실행 파일 생성
| 단계 | 설명 |
|---|---|
| 전처리 | #include, #define 등 전처리 지시문 처리 |
| 컴파일 | C 소스코드를 어셈블리 코드로 변환 |
| 어셈블 | 어셈블리 코드를 목적 파일로 변환 |
| 링크 | 목적 파일과 라이브러리를 연결하여 실행 파일 생성 |
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 언어는 운영체제 개발에 널리 사용되었다. 특히 UNIX 운영체제는 C 언어로 구현되었으며, 이후 많은 운영체제와 시스템 소프트웨어 개발에 영향을 주었다.
C 언어가 운영체제 개발에 적합한 이유는 다음과 같다.
- 메모리 직접 제어 가능
- 포인터 제공
- 비트 연산 지원
- 하드웨어 가까운 수준의 제어 가능
- 실행 속도 우수
- 컴파일 결과가 효율적임
C 언어는 임베디드 시스템 개발에서도 널리 사용된다.
이유는 다음과 같다.
- 제한된 자원에서 효율적인 실행 가능
- 하드웨어 레지스터 제어 가능
- 메모리 사용량을 세밀하게 관리 가능
- 컴파일러 지원 범위가 넓음
- 실시간 처리에 적합
C 언어의 장점은 다음과 같다.
- 실행 속도가 빠르다.
- 하드웨어 제어가 가능하다.
- 이식성이 높다.
- 문법이 비교적 단순하다.
- 운영체제와 시스템 소프트웨어 개발에 적합하다.
- 다양한 플랫폼에서 컴파일러를 사용할 수 있다.
- 자료구조와 알고리즘 학습에 적합하다.
C 언어의 단점은 다음과 같다.
- 메모리 관리를 개발자가 직접 해야 한다.
- 포인터 사용이 어렵고 오류가 발생하기 쉽다.
- 배열 경계 검사를 자동으로 수행하지 않는다.
- 문자열 처리가 불편하다.
- 메모리 누수와 버퍼 오버플로우 위험이 있다.
- 객체지향 기능이 기본적으로 제공되지 않는다.
- 안전성보다 성능과 제어에 초점이 강하다.
C 언어에서 자주 발생하는 오류는 다음과 같다.
- 포인터 오류
- 널 포인터 참조
- 배열 범위 초과
- 버퍼 오버플로우
- 메모리 누수
- 이중 해제
- 초기화되지 않은 변수 사용
- 형 변환 오류
- 세미콜론 누락
- 헤더 파일 누락
C 언어는 메모리 제어가 강력한 만큼 보안 취약점도 발생하기 쉽다.
대표적인 취약점은 다음과 같다.
- 버퍼 오버플로우
- 포맷 스트링 취약점
- Use-after-free
- Double free
- 널 포인터 역참조
- 정수 오버플로우
- 메모리 누수
안전한 C 프로그래밍을 위해서는 입력값 검증, 경계 검사, 안전한 문자열 함수 사용, 정적 분석, 코드 리뷰가 중요하다.
C++는 C 언어를 기반으로 객체지향 프로그래밍, 템플릿, 예외 처리 등 다양한 기능을 확장한 언어이다.
| 구분 | C | C++ |
|---|---|---|
| 주요 패러다임 | 절차적 프로그래밍 | 객체지향, 제네릭, 절차적 프로그래밍 |
| 클래스 | 없음 | 있음 |
| 함수 오버로딩 | 없음 | 있음 |
| 예외 처리 | 기본 제공 안 함 | 제공 |
| 주요 용도 | 시스템 프로그래밍, 임베디드 | 시스템, 응용, 게임, 고성능 프로그램 |
Java는 C/C++의 영향을 받았지만, 메모리 관리와 실행 방식에서 차이가 있다.
| 구분 | C | Java |
|---|---|---|
| 실행 방식 | 네이티브 코드로 컴파일 | 바이트코드와 JVM 기반 실행 |
| 메모리 관리 | 개발자가 직접 관리 | 가비지 컬렉션 사용 |
| 포인터 | 직접 사용 가능 | 직접 포인터 없음 |
| 플랫폼 독립성 | 소스 수준 이식성 | JVM 기반 이식성 |
| 주요 용도 | 시스템, 임베디드 | 웹, 서버, 모바일, 기업 애플리케이션 |
C 언어는 프로그래밍의 기초뿐 아니라 컴퓨터 구조, 운영체제, 메모리 관리, 자료구조를 이해하는 데 중요한 언어이다.
학습할 때는 다음 항목을 중심으로 정리하는 것이 좋다.
- 기본 자료형
- 연산자
- 제어문
- 함수
- 배열
- 문자열
- 포인터
- 구조체
- 동적 메모리 할당
- 파일 입출력
- 컴파일 과정
- 메모리 오류와 보안 취약점
- C 언어는 절차적 프로그래밍 언어이다.
- C 언어는 컴파일 언어이다.
- C 프로그램은 일반적으로 main 함수에서 실행을 시작한다.
- 포인터는 메모리 주소를 저장하는 변수이다.
- 배열 인덱스는 0부터 시작한다.
- 문자열은 널 문자 `\0`으로 끝나는 문자 배열이다.
- 구조체는 서로 다른 자료형을 하나로 묶는 사용자 정의 자료형이다.
- malloc, calloc, realloc은 동적 메모리 할당에 사용된다.
- free는 동적으로 할당한 메모리를 해제한다.
- C 언어는 배열 경계 검사를 자동으로 수행하지 않는다.
- 버퍼 오버플로우는 C 언어에서 대표적인 보안 취약점이다.
- 컴파일 과정은 전처리, 컴파일, 어셈블, 링크 순서로 설명할 수 있다.
