본문 바로가기

프로그래밍 언어/C

[K.N.K C Programming 정리] Chap 2. C Fundamentals

K.N.KING C Programming

안녕하세요 여러분!

2단원을 가져와봤습니다! C의 기초를 설명해주는 단원입니다. 새로 알게 된게 많았던거 같아요. ㅎㅎ 

바로 드가봅시다!


Chap2. C Fundamentals

One man's constant is another man's variable.


2.1 Writing a Simple Program

아주 간단한 코드와 함께 천천히 시작해보자.

pun.c 라는 프로그램을 작성했다고 해보자. 코드는 다음과 같다.

#include <stdio.h>

int main(void)
{
	printf("To C, or not to C : that is the question.\n");
    return 0;
}

우선은 첫 줄부터 보자. 

 

#include <stdio.h>

 

이것은C의 표준 I/O(input/output) 라이브러리의 정보를 여러분의 프로그램에 "포함"시키기 위해 필요하다. 

그리고 프로그램은 main 함수를 실행시킨다. printf 는 표준 출력을 수행하기 위해 표준 입출력(I/O) 라이브러리에서 가져온 함수이다. printf 끝의 '\n' 은 한 줄을 띄게 해준다. 

 

return 0;

 

이 문장은 프로그램이 끝날때 "0" 값을 운영체제(operating system)에게 "반환(return)" 한다는 것을 나타낸다. 

 

 

Compiling and Linking

위 코드는 꽤 간결하다. 그렇지? 그럼에도 불구하고 우리가 방금 작성한 pun.c 파일을 실행시키는 것은 꽤 골치아픈일이다.(getting pun.c to run is more involved than you might expect

먼저, 우리는 pun.c 라는 이름을 가지고 있고 해당 코드를 담은 파일을 만들어야 한다. (메모장같은 그 어떤 텍스트 에디터를 써도 이건 할 수 있다.) .c 확장자는 컴파일러 때문에 필요하다. 

그 다음으로, 우리는 프로그램을 기계가 실행할 수 있게 변환해야 한다. C 프로그램에서 이는 3단계로 나뉜다.

  • 전처리(Preprocessing) : 프로그램은 우선 전처리기(preprocessor)에게로 보내진다. 전처리기는 # 으로 시작하는 명령들(which known as directives) 을 처리한다. 전처리기는 사실 에디터에 가깝다. 전처리기는 프로그램에 컴파일러가 컴파일 하는것을 돕는 여러것들을 추가하고, 코드를 수정하기도 한다.
  • 컴파일(compiling) : 수정되고 바뀐 프로그램은 (modified program)  컴파일러로 보내진다. 컴파일러는 이를 0과 1로 이루어진 기계어로 번역한다.(translates it into machine instructions (object code)).  
  • 링킹(Linking) : 마지막 단계에선, 링커라는 녀석이 컴파일러에 의해 만들어진 object code와 완전하게 실행가능한 프로그램을 만드는데 필요한 추가적인 코드를 묶어준다. 추가적인 코드는 프로그램 안에서 실행되는 라이브러리 함수(printf)를 포함한다.

운좋게도, 이런 과정들은 자동화 되어있다! 그러니까 위 과정을 너무 부담스럽게 생각하지 말자. 

실제로는, 전처리기는 보통 컴파일러와 합쳐져 있다. 실무에 나가면 아마 이를 생각도 안할것이다.

컴파일과 링킹을 하기 위한 명령어들은 운영체제나 컴파일러에 따라 다르다. UNIX 운영체제 아래서 C컴파일러는 보통 cc라는 명령어를 사용한다. 터미널이나 커맨드창에서 

 

% cc pun.c

 

라고 입력하면 pun.c 파일의 컴파일과 링킹을 할 수 있다! 링킹은 cc를 쓰면 자동으로 할 수 있다는것.

컴파일과 링킹을 끝내고나면, cc 는 a.out(기본설정값임) 이라는 실행가능한 프로그램을 넘긴다. cc는 많은 옵션을 가지고 있다. 만약 .out 확장자 파일의 이름을 바꾸고 싶으면 -o 라는 옵션을 쓰면 된다. 즉,

 

% cc -o pun pun.c 

 

라고 입력하면 pun.out 이라는 실행가능한 프로그램을 얻을 수 있다.

(※ 참고 : gcc 컴파일러는 아마 가장 유명한 C 컴파일러중 하나일것이다. 리눅스에서 지원하는 컴파일러이다. 다른 플랫폼에서도 사용가능하다. gcc는 UNIX cc 컴파일러를 쓰는것과 비슷하게 사용할 수 있다. 무슨말이냐면, 위에서 한 것처럼 pun.c 파일을 컴파일 하기 위해 % gcc -o pun pun.c 라고 입력할 수 있다는 것이다.)

 

 

Integrated Development Environments

우리는 지금까지 "command - line" 컴파일러를 살펴봤다. 무슨말이냐면, os가 제공해주는 특별한 창을 열고 거기서 입력하는 방식으로 해왔단 것이다. (CLI GUI 를 떠올리시면 될듯합니다!) 근데 이렇게 하기 귀찮지? 그렇지?

이럴때는 IDE, integrated development environment 를 사용하면 된다. IDE란 코드를 편집하고, 컴파일하고, 링킹하고, 실행시켜주고, 심지어는 디버깅까지 같은 환경 내에서 가능하게 해주는 소프트웨어 패키지이다. 당신의 환경에 걸맞는 IDE를 설치해보아라.

 


2.2 The General Form of a Simple Program

우리가 적은 pun.c 프로그램을 좀 더 일반화 시켜보자. 간단한 C 프로그램들은 다음과 같은 형식으로 이뤄진다.

 

directives

 

int main(void)

{

    statements

}

 

 

중괄호가 main 함수가 시작하는 부분과 끝나는 부분을 가리키고 있다는걸 짚고 넘어가자! 이는 C의 간결성과도 연결된다. 아주 간단한 C 프로그램일지라도, 크게 세 부분으로 나눌 수 있다. 

directives(editing commands that modify the program prior to compilation)

functions(named blocks of executable code, of which main is an example)

statements(commands to be performed when the program is run)

이제 각각이 무엇인지 알아보자.

 

Direcitves

C 프로그램은 컴파일되기전에 전처리기에 의해 편집된다. 전처리기가 처리하는 문장을 direcives 라고 부른다.(Commands intended for the preprocessor are called directives) 14장과 15장에서 좀 더 자세히 다루기로 하고, 일단은 #include directive에 집중해보자.

 

#include <stdio.h>

 

이 directive 는 <stdio.h> 안에 있는 정보를 컴파일되기 전에 프로그램에 포함시켜준다. <stdio.h>는 C의 표준 입출력 라이브러리에 관한 정보를 포함하고 있다. C에는 stdio.h 와 같이 꽤 많은 헤더(headers)들이 있다. 

<stdio.h>를 포함시키는 이유는 C는 다른 언어들과 달리 내장된 입력 명령 혹은 출력 명령들이 없기 때문이다.

(has no built - in "read" and "write" commands). 

Directives 는 항상 # 으로 시작한다. 끝에 세미콜론이 붙지도 않는다.

 

 

Functions

함수는 두가지 분류로 나뉜다. 첫 번째는 프로그래머에 의해 작성된 것이고 두 번째는 C 구현의 일부로 주어진것이다. (those provided as part of the C implementation) 나(작가)는 이 중 후자를 라이브러리 함수(library functions) 이라고 부른다. "fuction" 이라는 용어는 수학에서 나왔다. C는 "function"이란 용어를 더 느슨하게 사용한다. C에서 함수(function)는 단순히 묶여져 이름을 가진 명령의 집합이다. (In C, a function is simply a series of statements that have been grouped together and given a name)

어떤 값을 계산하는 함수는 자신이 반환하는 값을 구체화 하기 위해 return 문을 사용한다. 

예를 들어 인자에 1을 더하는 함수는 return x + 1; 이란 문장을 실행할것이다.

비록 C 프로그램이 많은 함수들로 구성됬어도, 오직 main 함수만이 필수이다. main 은 특별한 함수이다. main 함수는 프로그램이 실행될때 자동으로 호출된다.

main 이 함수라면 main 함수도 결과값을 반환하는가? 그렇다!

main함수는 프로그램이 종료될때 os가 받는 '상태코드'라는것을 반환한다. (it returns a status code that is given to the operating system when the program terminates

다음와 같은 코드를 살펴보자.

 

#include <stdio.h>

int main(void)
{
    printf("To C, or not to C : that is the question.\n");
    return 0;
}

 

main 앞의 int는 main함수가 정수값을 반환한다는것을 의미한다. 소괄호 안의 void는 main 함수가 아무 인자를 가지지 않는다는것을 말한다. 

return 0; 이 문장은 두가지를 의미한다.

첫 번째는 main 함수가 자신을 종료하게 해주는 수단이란것과, 두 번째는 main 함수가 0 값을 반환한다는 것이다.

main함수의 반환값에 대해서는 이후의 챕터에서 알아보기로 하고, 일단은 프로그램의 정상 종료를 의미하는 0 을 반환한다고 생각하자.  이 때, return 문이 없어도 프로그램은 종료된다. 그러나 많은 컴파일러들은 경고 메시지를 띄울것이다! (int main, 즉 int 값을 반환해야되는데 반환 안하니까말이다.)

 

 

Statements

구문(statement) 는 프로그램이 실행될때 실행되는 문장을 의미한다.(A statement is a command to be executed when the program runs) pun.c 프로그램은 두 가지 구문을 포함한다. 첫 번째는 return 문과 두 번째는 function call(함수호출)이다. 함수에 자신이 맡은 역할을 수행하라고 하는것은 함수를 호출한다 라고 부른다.(Asking a fucntion to perform its assigned task is known as calling the fucntion) pun.c 프로그램은 printf 를 호출한다! 그 결과로 우리는 화면에 문자열이 표시되는걸 알 수 있다.

 

C는 각 구문들이(each statement) 세미콜론으로 끝나기를 요구한다. (단, compound statements 예외.) 세미콜론은 컴파일러에게 각 구문이 어디서 끝나는지 알려준다.


2.4 Variables and Assignment 

대다수의 프로그램들은 원하는 결과값을 도출해내기 위해 일련의 계산을 거쳐야한다. 이 과정에서 자연스레 프로그램 실행 중 데이터를 저장할 공간이 필요한데, 이러한 저장공간을 변수(Variables) 라고 부른다.

 

Types

모든 변수는 반드시 타입(type)을 가져야 한다. 타입은 해당 변수가 어떤 종류의 데이터를 들고 있는지 구분짓는다. C에는 많은 타입이 존재한다. 일단은 int와 float 만 다뤄보자. 적절한 타입을 고르는 것은 매우 중요하다. 왜냐하면, 타입은 변수가 어떻게 저장되는지, 그리고 해당 변수에 어떤 연산이 수행될 수 있는지에 영향을 미치기 때문이다.(Choosing the proper type is critical, since the type affects how the variable is stored and what operations can be performed on the variable) 숫자를 가지는 변수의 타입은 그 변수가 저장할 수 있는 가장 큰 수와 작은 수를 결정한다. 또한 숫자가 소수점아래로 허용되는지(즉 실수를 저장할 수 있는지)도 결정한다. 

int에는 정수를 저장할 수 있다. float에는 실수를 저장할 수 있는데, 보통 flaot 의 산술 연산은 정수의 산술연산보다 느리다. 가장 중요한것은, float 타입의 변수의 값은 단지 그 수의 어림값이다! (the value of a float variable is often just an approximation of the number that was stored in it). 0.1을 float 타입 변수에 저장하면 사실 그 변수의 값은 

0.99999999999999987 이다! 이후의 챕터에서 더 자세히 다루도록 하자. 

 

Declarations

변수는 사용되기 전에 반드시 선언되어야한다.(variables must be declaraed before they can be used) 변수를 선언하기 위해서, 먼저 변수의 타입을 정해야한다.

함수를 선언할때 반드시 선언부를 statements보다 먼저 선행되야한다. 예를 들면,

 

int main(void) 

{

    declaration

 

    statements

}

 

이런식으로 말이다. 

 

Assignment

변수는 할당을 통해 값을 부여받는다. (A variable can be given a value by means of assignment).

만약 int 값을 float변수에 할당하려하거나 float 값을 int 변수에 할당하려 하는 경우, 이는 가능하긴 한데 항상 안전한건 아니다. 이후의 챕터에서 다뤄보자. 

 

Initialization

어떤 변수들은 자동적으로 프로그램이 시작할떄 0이 저장된다. 그러나 대부분 그렇지 않다.

기본값을 가지지 않는 변수이거나 혹은 아직 할당받지 않은 변수는 초기화되지 않았다 라고 표현한다.

(A variable that doesn't have a default value and hasn't yet been assigned a value by the program is said to be uninitialized)

초기화되지 않는 변수를 사용하는건 매우 위험하다! 2568,-30891 같은 엉뚱한 숫자가 나올뿐더러 프로그램이 깨질수도 있다!

따라서 선언할때 초기화도 동시에 해주는 습관을 길러놓자. 이를 테면 다음과 같다. 

 

int height = 8;

 

C에서는 저 8이라는 숫자를 initializer 라고 부른다.

몇개의 변수라도 다음과 같이 같은 선언으로 초기화 할 수 있다.

 

int height = 8, length = 12, width = 10;

 

각가의 변수들이 자신만의 initializer를 필요로 한다는걸 짚고 넘어가자. 다음의 예시에서 initializer 10은 width에서만 유효하다.

 

int height, length, width = 10;

 

2.5 Reading Input

입력을 얻기 위해, scanf() 함수를 써보자. 

scanf 의 f는 printf에서 f와 마찬가지로, "formatted" 를 의미한다. scanf 와 printf 모두 입력과 출력의 형태를 정하기 위해 형식화된 문자열(format string)을 필요로 한다. 

scanf 는 printf 와 마찬가지로 입력값으로 어떤 값이 들어올지 미리 알고있어야 한다. 

정수값을 읽기 위해, 다음과 같이 scanf 를 쓸 수 있다. 

 

scanf("%d", &i); // reads an integer, stores into i

 

%d는 scanf 에게 정수값이 입력될거라는것을 알려준다. & 에 관한 설명은 이후의 챕터에서 하자.

scanf 는 여기서 맛만 보고 넘어가자! 3장에서 자세히 다룬다 ㅎㅎ

 

 

2.7 Identifiers

코드를 작성해나가면서, 우리는 변수의 이름, 함수, 그리고 매크로 등등의 이름을 정해야한다. 

이런 일므들은 식별자(identifiers) 라고 불린다. 

 

여기 식별자의 옳바른 예시들이 있다.

 

times10   get_next_char    _done

 

다음 예시는 옳지 못한 예시이다.

 

10times    get-next-char     

 

10times 는 숫자로 시작한다. get-next-char 는 _가 아니라 - 가 포함되어있다.

C는 문자에 매우 예민하다!(case - sensitive

많은 프로그래머들은 식별자에(매크로 예외) 오직 소문자, 그리고 구분을 위해 _ 만 넣는 규약(convention)을 따른다.

이를테면,

 

symbol_table         current_page      name_and_address

 

그런데 여기서 대문자를 섞는 방법도 있다.

 

symbolTable         currentPage      nameAndAddress

 

전자의 예시가 고전적인 C 스타일이라면, 후자는 Java 와 C# 때문에 점점 유명해지는 추세이다. 

다른 규약들도 많지만, 중구난방으로 여러 스타일을 섞어 쓰지말고, 한 스타일만 쭉 쓰자!

 

KeyWords

키워드는 예약어로 불린다. 키워드는 식별자로 사용될 수 없다! auto, enum, float, int, sizeof, while, _Bool 등이 있다.(검색해보면 다 찾을 수있다)

몇몇 컴파일러들은 우리가 쓰는 식별자들을 keyword로 사용하고 있을수도 모르니 조심해야한다.

 

 

Q&A

Q : GCC가 뭔가요?

A : GCC는 원래 GNU C Compiler 를 의미했으나, 현재는 GNU Compiler Collection 을 의미합니다. 여러 언어로 만들어져서 그렇습니다.

 

Q : GCC가 에러를 찾는데 좋나요?

A : GCC는 프로그램을 체크하는 여러 명령어 옵션을 지니고 있습니다. 이런 옵션들이 사용되면 GCC는 에러 가능성이 있는 부분을 쉽게 찾아줍니다! 예를 들면,

 

  • -Wall : 컴파일러가 에러 가능성이 존재하는 부분을 발견하면 경고메시지를 띄워줍니다. -W는 특정한 에러를 찾는데 사용되고, -Wall 은 "all -W options"를 의미합니다. -O와 사용되면 효과가 더 좋습니다.
  • -std=c99 : 코드를 컴파일하는데 어떤 C컴파일러가 쓰여야 하는지 구체화시켜준다.
  • -std=c89 : 이하동일

읽어주셔서 감사합니다! 다음편에서 뵐게요~ ㅎㅎ