1. Summary
정보처리기사 실기 시험을 준비하면서 C를 학습했습니다.
처음엔 막연히 무섭게만 생각했는데, 문제 풀이와 함께 문법 및 개념을 정리했더니,
문제를 풀 수 있을 정도로는 이해가 갑니다.
여전히 어렵게만 느껴져도, 프로그래밍 언어의 근본(?)이라는 이야기를 많이 들어,
꼭 알아야겠다는 마음이 듭니다.
이 챕터에선 C언어의 기본적인 개념과 원리를 살펴보겠습니다.
이번 글은 제가 궁금했던 언어의 기본 문법 및 개념들 위주로 간략히 살펴봅니다.
1. 자료형과 타입, 키워드
기본 자료형
| 자료형 | 크기(일반적) | 설명 |
|---|---|---|
char | 1바이트 | 문자 1개 저장 (ASCII 코드) |
int | 4바이트 | 정수형 기본 타입 |
short | 2바이트 | 짧은 정수형 |
long | 4~8바이트 | 긴 정수형 |
float | 4바이트 | 단정도 실수 |
double | 8바이트 | 배정도 실수 |
void | 0바이트 | 반환 값이나 포인터가 없음을 의미 |
double이 좀 낯설었는데, "8바이트 크기를 가지는 실수형(부동소수점) 데이터 타입"이라고 합니다.
float보다 더 넓은 범위의 값을 표현할 수 있으며, 소수점 이하 약 15자리까지의 정밀도를 가집니다.
키워드(예약어)
타입 앞에 키워드(예약어)가 붙는 경우가 있어 정리했습니다.
static, const는 낯익지만, 키워드가 놓이는 위치가 낯설게 느껴졌습니다.
struct: asdstatic: 지역 변수의 생명 주기 유지, 전역 변수의 접근 제한const: 상수화 (값 변경 불가)signed/unsigned: 부호 여부 지정 (unsigned int는 0 이상의 정수만)short,long: 저장 크기 변경volatile: 최적화 방지용 (하드웨어 접근 시 사용)extern: 외부 파일의 전역 변수 참조
포인터 관련 타입 선언
타입에 포인터가 붙거나, 변수명 앞에 붙는 경우가 어떤 차이인지 궁금하여 더 찾아봤습니다.
int *p;:int형 변수를 가리키는 포인터char **p;:char형 포인터를 가리키는 포인터(이중 포인터)struct node *p;: 구조체를 가리키는 포인터
ref-type
C 언어는 엄밀한 의미에서 "참조 타입"을 직접적으로 지원하지는 않는다고 합니다.
대신, **포인터(Pointer)**를 사용하여 메모리 주소를 직접 다루며,
이를 통해 다른 언어의 참조와 유사한 역할을 수행합니다.
C에서 배열이나 구조체(struct) 변수를 사용하기 위해 {}를 사용합니다.
리스트(배열) 타입은 여러 개의 값을 묶어서 하나의 변수에 할당하기 위해 사용합니다.
2. 포인터
개념
포인터(pointer)는 메모리 주소를 저장하는 변수입니다.
변수의 값을 직접 저장하지 않고, 변수의 주소를 가리켜 간접적으로 접근하게 됩니다.
앞서 살핀 참조 타입의 리스트, 구조체나 함수와 함께 많이 사용됩니다.
| 자료형 | 크기(일반적) | 설명 |
|---|---|---|
* (포인터) | 4 또는 8바이트 | 변수의 메모리 주소를 저장하는 타입 |
& (주소 연산자) | - | 변수의 주소를 얻는 데 사용되는 연산자 |
* (역참조 연산자) | - | 포인터가 가리키는 메모리 위치의 값을 가져오는 데 사용되는 연산자 |
void * | 4 또는 8바이트 | 모든 타입의 주소를 저장할 수 있는 제네릭(Generic) 포인터 |
주요 특징
다음과 같은 특징이 있습니다. 코드와 문법을 함께 살피면 이해가 빠릅니다.
- 주소 저장: 포인터는 값 자체가 아니라, 값이 저장된 메모리 공간의 시작 주소를 보관합니다.
- 간접 접근: 포인터를 통해 원본 데이터가 저장된 메모리 위치에 간접적으로 접근하여 데이터를 읽거나 수정할 수 있습니다.
- 동적 메모리 할당:
malloc(),calloc()등의 함수를 사용하여 힙(Heap) 영역에 메모리를 동적으로 할당하고 포인터로 관리합니다. - 함수로 데이터 전달: 함수 호출 시 파라미터로 포인터를 전달하면, 함수 내에서 원본 변수의 값을 직접 변경할 수 있습니다 (
Call-by-Reference효과). - 크기: 포인터 변수의 크기는 운영체제(CPU 아키텍처)에 따라 달라지며, 32비트 시스템에서는 4바이트, 64비트 시스템에서는 8바이트가 일반적입니다. (가리키는 데이터의 타입 크기와는 무관합니다.)
문법과 역할
int a = 10;
int *p = &a; // a의 주소를 p에 저장
- *p : 포인터가 가리키는 주소의 값(
dereference) - &a : 변수 a의 주소(
address-of)
포인터 위치에 따른 의미 차이
| 선언 | 의미 |
|---|---|
int* p; | p는 int를 가리키는 포인터 |
int *p, q; | p는 포인터, q는 일반 int |
int* p1, *p2; | p1과 p2 모두 int 포인터 |
*은 변수 단위에 적용되므로, 혼동하기 쉬워 주의가 필요합니다.
Call by Value/Reference
함수 파라미터에 포인터를 사용하여 스코프 밖의 변수값을 변경할 수 있습니다.
- 값에 의한 호출(Call by Value): 인자 값을 복사하여 함수로 전달
- (원본 변수에는 영향 없음)
- 참조에 의한 호출(Call by Reference)
- **인자의 주소(포인터)를 전달 **(원본 값 변경 가능)
함수, 스코프 챕터에서 관련 내용을 더 살펴보겠습니다.
포인터 연산 우선순위
포인터 연산에서 우선순위가 혼동되어 간략히 표로 정리했습니다:
| 연산자 | 의미 | 우선순위 |
|---|---|---|
| *p | 포인터가 가리키는 값 | 높음 |
| p++ | 다음 메모리 주소(타입 크기만큼 이동) | 중간 |
| (*p)++ | 포인터가 가리키는 값 증가 괄호로 구분 | 필요 |
| &*p | 다시 주소로 변환 (p와 동일) 주의: | 우선순위는 *보다 &가 낮음 |
3. 함수와 스코프
함수의 개념
함수(function)는 특정 기능을 수행하는 코드 블록으로, 반복되는 코드를 재사용하고 프로그램을 모듈화하는 데 사용됩니다.
함수의 기본 구조
반환타입 함수명(매개변수1, 매개변수2, ...) {
// 함수 본문
return 반환값;
}
주요 특징
-
선언(Declaration)과 정의(Definition)
- 선언: 함수의 프로토타입(시그니처)만 작성 (
int add(int a, int b);) - 정의: 함수의 실제 구현 코드 작성
- 선언은 호출 전에 필요하며, 정의는 한 번만 작성
- 선언: 함수의 프로토타입(시그니처)만 작성 (
-
반환 타입
void: 반환값이 없을 때 사용int,char,double등: 해당 타입의 값을 반환- 포인터나 구조체도 반환 가능
-
매개변수(Parameter)
- 함수 호출 시 전달받는 값
- 기본적으로 값 복사(Call by Value) 방식
- 포인터를 사용하면 원본 수정 가능(Call by Reference)
스코프(Scope)
스코프는 변수가 접근 가능한 영역을 의미합니다.
변수는 선언된 위치에 따라 접근 범위가 결정됩니다.
스코프의 종류
| 스코프 | 설명 | 생명주기 |
|---|---|---|
| 전역 스코프 | 함수 밖에서 선언된 변수 | 프로그램 종료 시까지 |
| 지역 스코프 | 함수 내부 또는 블록({}) 내에서 선언된 변수 | 블록을 벗어나면 소멸 |
| 파라미터 스코프 | 함수의 매개변수 | 함수 호출 시 생성, 종료 시 소멸 |
변수의 생명주기
- 자동 변수(지역 변수): 블록 진입 시 생성, 블록 종료 시 자동 소멸
- 전역 변수: 프로그램 시작 시 생성, 종료 시 소멸
- 정적 변수(
static): 프로그램 시작 시 생성, 종료 시 소멸 (단, 선언된 블록 내에서만 접근 가능)
static 키워드의 스코프 적용
앞서 키워드 섹션에서 static을 다뤘는데, 스코프에서의 역할을 정리하면:
- 지역 변수에
static사용- 변수의 생명주기가 프로그램 종료까지 유지됨
- 함수 호출 간 값이 유지되어 상태를 보존할 수 있음
void counter() {
static int count = 0; // 함수 호출 간 값 유지
count++;
printf("%d\n", count);
}
- 전역 변수에
static사용- 해당 파일 내에서만 접근 가능 (다른 파일에서
extern으로 참조 불가) - 파일 스코프 제한 효과
- 해당 파일 내에서만 접근 가능 (다른 파일에서
함수와 포인터
함수에서 포인터를 사용하면 Call by Reference 방식으로 원본 값을 수정할 수 있습니다. 포인터 섹션에서 언급한 내용의 실제 사용 예시입니다.
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
- 매개변수로 포인터를 전달하면 함수 내에서 원본 변수의 값을 변경할 수 있습니다.
- 배열도 포인터로 전달되어 함수 내에서 배열 요소를 수정할 수 있습니다.
4. 표준 라이브러리
C 언어의 표준 라이브러리는 프로그래밍에 필요한 기본 기능들을 제공합니다.
각 헤더 파일(.h)을 #include로 포함하여 사용합니다.
stdio.h (Standard Input/Output)
입출력 관련 함수들을 제공하는 표준 라이브러리입니다.
콘솔, 파일, 문자열을 대상으로 데이터를 읽고 쓸 수 있습니다.
| 함수 | 설명 |
|---|---|
printf() | 형식 지정 출력 (콘솔) |
scanf() | 형식 지정 입력 (콘솔) |
putchar() / getchar() | 문자 단위 입출력 |
puts() / gets() | 문자열 단위 입출력 (단, gets()는 보안상 사용 금지) |
fopen() / fclose() | 파일 열기/닫기 |
fread() / fwrite() | 파일 입출력 (바이너리 모드) |
주요 포인트
printf()와scanf()는 형식 지정자(%d,%s,%f등)를 사용하여 다양한 타입의 데이터를 입출력합니다.- 파일 작업 시
fopen()으로 파일을 열고, 사용 후 반드시fclose()로 닫아야 합니다. gets()함수는 버퍼 오버플로우 보안 취약점이 있어 사용을 지양하며,fgets()를 대신 사용하는 것이 권장됩니다.
stdlib.h (Standard Library)
표준 유틸리티 함수들을 제공하는 라이브러리입니다.
동적 메모리 관리, 타입 변환, 난수 생성 등 다양한 기능을 포함합니다.
| 함수 | 설명 |
|---|---|
malloc(size) | 동적 메모리 할당 (초기화 X) |
calloc(n, size) | 동적 메모리 할당 (0으로 초기화) |
realloc(ptr, new_size) | 기존 메모리 크기 재조정 |
free(ptr) | 동적 메모리 해제 |
rand() / srand() | 난수 생성 / 시드 설정 |
atoi() / atof() | 문자열을 숫자(int, double)로 변환 |
abs() | 정수 절댓값 반환 |
exit(status) | 프로그램 종료 |
system(cmd) | 시스템 명령 실행 |
주요 포인트
malloc()과calloc()은 힙(Heap) 영역에 메모리를 할당합니다. 포인터 섹션에서 언급한 동적 메모리 할당과 관련됩니다.malloc()은 메모리를 초기화하지 않지만,calloc()은 0으로 초기화됩니다.- 동적으로 할당한 메모리는 반드시
free()로 해제해야 메모리 누수를 방지할 수 있습니다. atoi()와atof()는 문자열의 시작 부분부터 숫자로 변환하며, 변환할 수 없는 문자가 나오면 중단합니다.