c-언어-기초
C 기초 배우기

C언어는 프로그래밍 언어 중에서 가장 오래된 언어이자 현재도 많은 분야에서 사용되는 언어입니다. 저수준(low level)에서 고성능을 발휘해야 하는 분야에서는 대부분 C를 사용할 만큼 C언어는 많은 프로그래밍 분야에서 핵심적인 역할을 해내고 있습니다. 이번 시간에는 C언어를 소개하고 C언어를 사용하기 위해 실습 환경을 설정하는 법 그리고 C언어의 기초 문법을 실습하는 시간을 가져보겠습니다. 그럼 시작해볼까요?

소개

C는 1972년 미국 벨연구소의 전산학자였던 데니스 리치(Dennis Ritchie)와 켄 톰슨(Ken Thompson) 유닉스(Unix) 운영체제[1]에서 사용하기 위해 개발한 프로그래밍 언어입니다. 초기 유닉스는 대부분 어셈블리어(assembly language)로 작성되었습니다. 어셈블리어는 기계어(machine language)와 1:1로 대응되도록 만들어진 언어입니다. 0과 1로 된 기계어를 사람이 이해하기 쉬운 알파벳 형태로 바꿔놓은 언어죠.

어셈블리어는 기계어에 비해 사용하기 쉽습니다. 숫자의 나열보다는 사람의 언어인 알파벳이 더 직관적이고 인식하기 편하기 때문이죠. 하지만 어셈블리어는 큰 단점이 있습니다. 바로 기계에 종속적이라는 점이죠.

기계어는 CPU의 종류에 따라 서로 다른 코드를 갖습니다. 이에 따라 기계어와 1:1로 대응되는 어셈블리어도 다른 CPU를 사용할 때마다 달라져야 합니다. 개발자는 기계를 변경할 때마다 어셈블리어를 각 기계에 적합하도록 고쳐서 사용해야 합니다. 한 기계에서 잘 작동하는 어셈블리어를 다른 기계에서는 사용할 수 없는 것이죠.

이들은 사용하고 있는 기계의 종류와 관계없이 범용적으로 쓰일 수 있는 언어를 만들고 싶었습니다. 그래야 특별히 더 작업을 하지 않고도 유닉스를 더 많은 기계에서 사용할 수 있었으니까요. 이에 데니스 리치는 켄 톰슨이 개발한 B언어를 개선해 C언어를 개발합니다.

C언어는 어셈블리어가 지닌 이식성(portability)의 문제를 해결합니다. 어셈블리어는 CPU에 따라 고쳐서 사용해야 했지만 표준을 지키면서 만든 C 코드는 따로 수정하지 않고도 기종이 다른 컴퓨터에 가져갔을 때 잘 동작합니다[2]. 또한 C언어는 당시 프로그래밍의 수준을 획기적으로 발전시킵니다. C언어는 어셈블리어보다 사람이 사용하기 쉬우면서도 어셈블리어처럼 저수준에서 하드웨어를 직접 제어하고 메모리를 최적화해서 효율을 높일 수 있었기 때문입니다. 그래서 C는 성능이 우선되는 분야에서 사용하기 적합합니다.

C언어를 사용하는 대표적인 분야는 시스템과 데스크톱 응용 프로그램 개발부터 하드웨어 장치 드라이버 및 대용량 서버 개발입니다. 특히, C언어는 크기와 자원이 제한된 환경에서 고효율을 내야 하는 임베디드 프로그래밍(embedded programming)이나 고성능 하드웨어가 필요한 게임 개발 분야에서 많이 사용됩니다.

그럼 프로그래밍 공부를 시작하는 사람들이 C언어를 익히면 좋은 이유는 무엇일까요? C언어를 배우면 더 효율적인 프로그램을 설계하는 데 도움이 됩니다. C언어는 하드웨어 수준에서 메모리를 직접 제어할 수 있습니다. 그래서 컴퓨터의 기본 동작 원리를 더 잘 익힐 수 있고 메모리를 고려하면서 프로그래밍하는 법을 배울 수 있기 때문이죠.

무엇보다 C언어를 익히면 다른 언어를 익히기 쉽습니다. 많은 언어가 C에서 파생되어 만들어졌습니다. 게임 프로그래밍에 많이 사용되는 C++와 C#은 물론 모바일 개발에 사용하는 자바(Java)나 데이터 사이언스에서 사용하는 파이썬(Python)에서 사용하는 클래스(class)라는 개념도 C언어의 구조체(structs)로부터 만들어졌습니다. C언어를 배우는 것은 다른 언어를 익히기 전에 먼저 라틴어를 공부하는 것처럼 언어의 뿌리를 배우는 것과 같습니다.

지금까지 C의 역사와 C가 사용되는 분야, 그리고 프로그래밍을 공부하는 사람들이 C를 배우면 좋은 이유를 살펴보았습니다. 그럼 지금부터는 C언어를 실습할 수 있도록 환경 설정하는 법을 알아보겠습니다.

환경 설정

C언어를 사용하려면 컴파일러(compiler)가 있어야 합니다. 컴파일러란 사람의 프로그래밍 언어를 컴퓨터가 이해할 수 있는 언어로 변환해주는 프로그램입니다. 컴파일러는 사람과 컴퓨터의 소통을 돕는 통·번역사라고도 할 수 있죠. C언어는 대표적인 컴파일 언어입니다. 컴파일 언어에 대한 자세한 내용은 프로그래밍 언어 종류와 언어별 특징를 참조해주세요.

C언어의 컴파일러로는 Dev-C++, 이클립스(eclipse), 비주얼 스튜디오(Visual Studio) 등을 사용할 수 있습니다. 여기서는 비주얼 스튜디오 커뮤니티 2022(Visual Studio Community 2022)를 사용하겠습니다. 비주얼 스튜디오 커뮤니티 2022의 자세한 설치 방법은 C언어를 위한 Visual Studio 2022 환경 설정하기를 참고해주세요.

환경 설정이 완료되었다면 지금부터는 본격적으로 C의 기본 코드 구조를 알아보겠습니다. 여기서는 C99를 기준으로 코드를 작성하겠습니다[3].

기본 코드 구조

C언어의 기본 코드 구조는 다음과 같습니다.

c
#include <stdio.h>
int main(void)
{
printf("Hello, World!\n");
return 0;
}

코드를 한 줄씩 살펴보겠습니다.

표준 라이브러리 가져오기

c
#include <stdio.h>

첫 번째 코드는 C 표준 라이브러리(C standard library)에서 핵심 입력과 출력 함수들을 정의하는 stdio.h 파일의 내용을 현재 파일에 복사해온다는 의미입니다.

여기서 # 기호(stringizing operator, number-sign)는 전처리기 연산자(preprocessor operator) 중에서 매개변수를 문자열로 변환하는 연산자입니다. 전처리기(preprocessor)란 우리가 작성한 소스 코드를 실행 파일로 만드는 빌드(build) 과정의 첫 번째 단계에서 두 번째 단계인 컴파일 과정을 위해 소스 코드를 가공하는 프로그램을 말합니다. 이 과정을 전처리(preprocessing)라고 합니다. 전처리 과정은 아래 컴파일 과정에서 조금 더 자세히 다루겠습니다.

# 기호 다음에는 includedefine과 같은 단어가 나옵니다. # 기호와 특정 단어가 합쳐진 이 구문을 전처리기에 특정 작업을 수행하도록 지시한다고 해서 전처리기 지시문(preprocessor directives)이라고 합니다.

#include 구문은 홑화살괄호(<>) 또는 큰따옴표("") 안에 있는 헤더 파일을 현재 이 파일에 포함하겠다고 전처리기에 명령을 내리는 역할을 합니다[4]. C언어에서는 함수를 사용하기 전에 함수의 원형을 선언해야 하는데 #include 구문이 이 일을 해주는 것이죠. #include 구문은 특정 파일을 현재 위치에 첨부해 하나의 파일처럼 컴파일해줍니다.

#include 다음에 나온 stdio.h는 표준입출력 라이브러리(STanDard Input Output library)의 줄임말로 헤더 파일(header file)입니다. 이 파일에는 출력함수인 printf 등 자주 사용하는 입출력 함수들이 정의되어 있습니다.

int main(void)과 중괄호

main 함수는 C언어의 가장 기본적인 함수입니다. main 함수는 "프로그램의 시작"을 의미하며 프로그램에 반드시 있어야 합니다. main 함수가 종료되면 프로그램이 종료됩니다. 여기서 int는 함수 반환값의 자료형(data type)이 정수라는 것을 알려줍니다. voidmain 함수가 인자값을 받지않는다는 것을 의미합니다. void는 관례처럼 사용하며 사용하지 않아도 됩니다.

int main(void)는 항상 중괄호({})와 함께 사용됩니다. int main을 머리(head)라고 하고 중괄호({})를 포함한 부분을 몸통(body)라고 합니다. 머리에는 함수의 이름과 필요한 데이터를 표시합니다. 함수에서 실행할 일은 모두 몸통의 중괄호({}) 안에 작성합니다. 모든 코드는 이 중괄호 안에 작성합니다. 여기서는 printf("Hello, World\n");라는 코드를 사용했습니다.

printf

printf 함수는 문자열을 출력하는 함수입니다. 함수 이름은 print formatted에서 유래했습니다. 이 함수에서는 큰따옴표("") 안에 있는 문자 또는 문자열이 출력됩니다. 여기서는 Hello, World!라는 문자열을 터미널에 출력하죠. 이 함수를 이용해 문자열을 출력할 때는 언제나 문자열을 큰따옴표("")로 감싸주어야 합니다.

제어 문자

코드의 맨 마지막에 보이는 \n은 줄 바꿈 기능을 수행하는 이스케이프 문자(escape character)입니다. 상태를 바꾸는 일련의 문자열이라는 뜻에서 이스케이프 시퀀스(escape sequence) 라고도 불립니다. 이스케이프 시퀀스는 출력되지 않으면서 줄 바꿈 문자, 작은따옴표, 또는 문자 상수의 다른 특정 문자 등을 나타내기 위해 사용합니다. \n은 개행문자(line feed)로 커서를 한 칸 아래로 이동해 새로운 행을 추가해줍니다.

이스케이프 시퀀스에서는 일반 문자가 아니라 특수 문자라는 것을 알리기 위해 백슬래시()를 사용합니다. 백슬래시는 윈도우(Windows) 운영체제에서는 원 기호(₩)로 보입니다.

return

C언어에서 return 예약어는 함수의 종료 또는 값을 반환한다는 의미로 사용됩니다. return이 실행되면 해당 함수는 즉시 종료됩니다. 여기서도 main 함수 마지막에 return 0;을 넣어 프로그램을 종료했습니다.

운영체제의 쉘(shell)에서는 0은 참(True)인 값, 정상을 의미합니다. 0이 아닌 다른 값은 에러를 의미합니다. 에러 코드는 다양할 수 있지만 정상은 한 가지 경우만 있기 때문에 0을 기본값으로 사용합니다. 따라서 return 0;은 프로그램을 정상적으로 종료한다는 것을 의미합니다.

세미콜론

세미콜론(;)은 문장의 끝을 알려줍니다. 마침표(.)와 같은 역할을 하죠. printf 함수와 return 구문의 마지막에 세미콜론을 사용한 것을 알 수 있습니다. 코드의 마지막에는 항상 세미콜론을 사용해야 합니다.

지금까지 C언어의 기본 코드 구조를 살펴보았습니다. 그럼 우리가 작성한 소스 코드는 컴퓨터가 바로 실행할 수 있을까요? 아닙니다. 소스 코드는 사람이 이해할 수 있는 고수준 언어(high-level language)로 작성했습니다. 소스 코드는 컴퓨터가 이해할 수 있는 저수준 언어(low-level language)로 바꿔야 합니다. 소스 코드 파일을 어떻게 컴퓨터가 이해할 수 있는 파일로 바꿀 수 있을까요?

빌드 과정

작성한 소스 코드가 컴퓨터에서 실행되려면 빌드 과정(build process)을 거쳐야 합니다. 빌드(build)란 컴퓨터가 실행할 수 있는 실행 파일(*.exe)을 생성하는 것을 의미합니다. C언어의 빌드 과정은 전처리(preprocessing), 컴파일(compiling), 어셈블(assempling), 링킹(linking)의 총 4단계를 거칩니다. 빌드 과정은 다음과 같습니다.

c-언어-빌드-과정
4단계 빌드 과정 | Snug Archive

빌드 과정의 첫 번째 단계는 전처리입니다. 앞서 잠깐 다루었죠. 전처리 과정은 컴파일하기 위해 소스 코드를 재구성하는 단계입니다. 이 단계에서는 다음과 같은 작업을 수행합니다.

  • #include 지시문을 통해 헤더 파일(*.h)을 현재 위치에 포함
  • #define 문에서 정의한 매크로를 모두 정의된 값으로 치환
  • 주석 제거

전처리 과정을 거치면 소스 파일(.c)이 전처리기를 통해 전처리 출력 파일(.i)로 변환됩니다. 전처리 과정이 끝나면 빌드 과정의 두 번째 단계인 컴파일 과정이 진행됩니다.

컴파일 과정은 전처리된 소스 코드를 어셈블리 코드로 변환하는 단계입니다. 컴파일러는 전처리된 파일(.i)을 어셈블리어 파일(.s)로 변환해줍니다. CPU는 어셈블리어를 이해하지 못하기 때문에 어셈블리어 파일은 CPU가 이해할 수 있는 기계어로 변환되어야 합니다.

빌드 과정의 세 번째 단계인 어셈블 과정에서 어셈블러(assembler)는 어셈블리어 파일을 목적 파일(*.obj)로 변환해줍니다. 오브젝트 파일로도 불리는 목적 파일은 이진 코드로 이루어진 바이너리 파일(binary file)입니다. 목적 파일은 다시 빌드 과정의 마지막 단계인 링킹(linking)을 거칩니다.

링킹 과정에서는 여러 개의 오브젝트 파일을 라이브러리 파일과 스타트업 코드(startup code)와 합쳐 단일 실행 파일을 만듭니다. 링커(linker)라는 프로그램이 이를 수행하죠. 스타트업 코드는 main 함수가 실행되기 전 프로그램을 시작할 때 필요한 작업을 기술한 코드로 main 함수를 호출해주는 역할을 합니다. 링킹 과정이 끝나면 비로소 컴퓨터가 실행할 수 있는 실행 파일(*.exe)이 만들어집니다.

지금까지 C언어의 기본 코드 구조와 코드가 실행파일로 만들어지는 빌드 과정을 살펴보았습니다. 그럼 지금부터는 C언어의 기초 문법을 실습해보는 시간을 갖겠습니다. 이번 시간에는 가장 기초적인 문법으로 변수(variables)와 상수(constant)를 선언하는 법을 알아보겠습니다.

실습

C언어에서는 데이터를 변수와 상수 형태로 할당할 수 있습니다. 변수란 데이터 값이 저장된 메모리 공간의 주소 이름입니다. 변수는 데이터에 접근할 수 있는 문과도 같습니다. 상수는 변하지 않는 값입니다. 한 번 저장하면 바꿀 수 없는 값이죠.

C언어의 자료형에는 정수형(integer), 실수형(float), 문자형(character)이 있습니다. 실습을 하면서 각 자료형을 차례로 알아보겠습니다. 먼저 정수형 변수부터 살펴볼까요?

1) 변수

정수형 변수

정수형 변수를 사용할 때는 int로 변수 앞에 자료형을 선언해주어야 합니다. int와 같이 기능적 의미가 정해져 있거나 뜻의 의미가 이미 예약된 단어를 키워드(keyword), 예약어(reserved word)라고 합니다. 정수형 변수를 사용할 때는 변환 문자 %d를 사용해서 정수형 서식을 지정해줍니다. 예제 코드는 다음과 같습니다.

c
#include <stdio.h>
int main(void)
{
int carrots = 5;
printf("%d\n", carrots); // 정수형 값을 출력한다
return 0;
}
# 결과
5

실수형 변수

실수형 변수의 키워드는 floatdouble입니다. 실수형 변수를 출력할 때는 %f 서식 지정자를 사용합니다. 만약 %.숫자f 와 같이 지정하면 소수점 이하 몇 자리까지 표시할 것인지를 지정할 수 있습니다. 예를 들어 %.2f면 소수점 이하 2자리까지 출력하겠다는 것을 의미합니다.

floatdouble의 차이점은 바이트의 크기입니다. float 은 4바이트, double은 8바이트를 차지합니다. 예제 코드는 다음과 같습니다.

c
#include <stdio.h>
int main(void)
{
float f = 32.5f;
printf("%f\n", f); // 소수점 이하 6자리까지 출력, %f 대신 %lf 사용 가능
printf("%.2f\n", f); // 소수점 이하 2자리까지 출력
double d = 25.521;
printf("%.1f\n", d);
return 0;
}
# 결과
32.500000
32.50
25.5

문자형 변수

문자형 변수를 선언할 때는 char 키워드를 사용합니다. 문자는 작은따옴표로 묶습니다. 서식 지정자로는 "%c"를 사용합니다. 예제 코드는 다음과 같습니다.

c
#include <stdio.h>
int main(void)
{
char c = 'A'; // 문자를 작은따옴표로 묶음
printf("%c\n", c);
return 0;
}
# 결과
A

문자열 변수

문자열(strings) 변수를 선언할 때도 문자와 같이 char 키워드를 사용합니다. 문자열은 큰따옴표로 묶습니다. 문자열 변수를 사용할 때는 크기를 함께 선언해야 합니다.

char 배열 이름[크기] = "문자열";

예제 코드는 다음과 같습니다.

c
#include <stdio.h>
int main(void)
{
char s[15] = "Hello, World!"; // 크기가 15인 char형 배열 선언, 문자열을 큰따옴표로 묶어 할당
printf("%s\n", s);
}
# 결과
Hello, World!

이번에는 상수를 알아보겠습니다.

2) 상수

상수를 정의하려면 const를 사용하면 됩니다. 다음은 올해 연도를 출력하는 코드입니다.

c
#include <stdio.h>
int main(void)
{
const int YEAR = 2022; // YEAR는 상수로 정의되었기 때문에 값을 바꿀 수 없음
printf("올해: %d\n", YEAR);
return 0;
}

지금까지 C언어 기초의 첫 번째 시간으로 C언어의 기초 개념을 살펴보았습니다. 다음 시간에는 C언어 기초의 두 번째 시간으로 C언어 조건문을 알아보겠습니다. 모두 수고 많으셨습니다.

참고 문헌

  • [1] 서현우,『혼자 공부하는 C 언어: 1:1 과외하듯 배우는 프로그래밍 자습서』, 한빛미디어, 2021, 3-22.
  • [2] Peter Ahn, 「[C언어 강좌-1] Welcome to Hello world」, Peter의 우아한 프로그래밍:티스토리, "https://gracefulprograming.tistory.com/9?category=922069"

  1. 유닉스는 벨 연구소에서 개발된 운영 체제로, 윈도우 계열이 아닌 리눅스(Linux), 안드로이드(Android), macOS, iOS 운영체제의 원형이 됨
  2. C언어의 이식성이 좋다는 말은 어셈블리어에 비교해 좋다는 의미로 자바(Java)와 같이 운영체제에 관계없이 작동할 수 있는 언어만큼 이식성이 좋다는 의미는 아님
  3. C언어에는 전 세계에서 사용하는 국제 표준이 있음. 이 국제 표준은 ANSI C, C89, C90, C99, C11로 발전함. C99는 1999년도에 C11은 2011년도에 만들어진 표준으로 이 두 가지 표준안이 가장 보편적으로 사용됨
  4. 헤더 파일이 표준 라이브러리일 때는 <>를 사용자가 정의한 라이브러리일 때는 ""를 사용
...

©2022 Snug Archive. All rights reserved.

Contact me at snugarchive@gmail.com.