윤성우의 열혈 C 프로그래밍 (Chapter 01 ~ 04)
C언어는 프로그래밍 언어이다.
사람은 컴퓨터의 기계어를 알지 못하고 컴퓨터는 사람의 말을 이해하지 못한다. 컴퓨터와 대화를 하려면 기계어를 알던가 컴퓨터가 사람의 말을 이해할수 있어야 하는데 그것은 불가능하다. 그래서 컴파일러라는 통역사를 두고 그 통역사가 이해할수 있고 사람이 익히기에도 기계어보다 훨씬 간단한 프로그래밍 언어를 사용하여 컴파일러에게 전달해주고 컴파일러가 컴퓨터에게 컴파일 해주는 것이다.
프로그래밍 언어란?
사람과 컴파일러가 이해할 수 있는 약속된 형태의 언어인 프로그래밍 언어를 의미한다.
컴파일러의 역할은 무엇인가?
프로그래밍 언어로 작성한 프로그램을 컴퓨터가 이해할 수 있도록 기계어로 번역하는 역할을 한다.
더불어 이렇게 번역하는 일 자체를 가리켜 '컴파일(compile)'이라 한다.
통역사의 역할을 하는셈.
기계어(Machine Language)란?
기계어란 컴퓨터가 이해할 수 있는 0과 1로 구성된 언어체계를 뜻한다. 물론 많은 노력을 통해서 사람도 이러한 언어체계를 습득할 수 있으나 특별한 목적 없이 이러한 기계어를 익히기 위해서 노력할 필요는 없다.
C언어는 1971년경에 UNIX라는 운영체제의 개발을 위해 Dennis Ritchie와 Ken Thompaso이 함께 설계한 범용적인 고급(high-level)언어이다.
저급, 고급언어의 구분기준은 컴퓨터가 이해하기 쉬운지 사람이 이해하기 쉬운지에 따라 나뉘고 고급언어 일수록 사람이 더욱 이해하기 쉬운언어 이다. 각각의 언어가 지니는 장단점도 다르기 때문에, 무조건 고급언어가 저급언어보다 좋다고 판단하는 것은 무리가 있다.
C언어의 장점
C언어는 절차지향적 특성을 지니기 때문에 익숙해지는데 오랜 시간이 걸리지 않는다.
-'정해진 순서의 실행흐름'을 중시한다는 의미이다. 인간의 일반적인 생각방식은 절차지향에 가깝기 때문에 C언어를 처음 접하는 사람도 조금만 공부하면 쉽게 익숙해질 수 있다.
C언어로 작성된 프로그램은 이식성이 좋다.
C언어로 작성된 프로그램은 CPU의 종류에 상관없이 실행이 가능하고, 운영체제의 차이에도 덜 민감하다.
C언어로 작성된 프로그램은 좋은 성능을 보인다.
언어의 특성보다 중요한 것이 만드는 방법이기 때문에 절대적인 것은 아니지만, 기본적으로 C언어는 성능에 민감한 소프트웨어의 개발에 주로 사용된다. 사용하는 메모리의 양이 상대적으로 적고, 속도를 저하시키는 요소들을 최소화한 언어이기 때문이다.
C언어의 단점
가장 대표적인 단점은 저급언어의 특징을 함께 지니기 때문에, 고급언어에 비해 더 주의해서 사용해야 한다는 점이다.
C 프로그램 완성과정의 전체적인 이해/
첫 번째 단계 프로그램의 작성
두 번째 단계 작성한 프로그램의 컴파일
세 번째 단계 컴파일 된 결과물의 링크
세 번째 단계인 링크는 보통 컴파일의 과정에서 함께 진행되기 때문에, 프로그램의 완성과정을 첫 번째와 두 번째로 줄여서 이야기하기도 한다.
Chapter 02
포로그램의 기본구성
C언어의 기본단위인 '함수'
"C언어는 함수로 시작해서 함수로 끝난다" C언어로 프로그램을 작성한다는 것은 '함수를 만들고, 만든 함수들의 실행순서를 결정하는 것'이기 때문이다.
3x+4=y
이 식에 x=2 를 대입하면 y = 10 이 된다. C언어에서는 x에 삽입되는 값을 '입력', 그 결과로 얻는 y의 값을 '출력' 이라고 한다.
그리고 적절한 입력과 그에 따른 출력이 존재하는 것을 가리켜 '함수(function)' 라고 한다.
함수의 정의 만들어진 함수, 실행이 가능한 함수를 일컬음
함수의 호출 함수의 실행을 명령하는 행위
인자의 전달 함수의 실행을 명령할때 전달하는 입력 값
int main(void)
"출력의 형태가 int이고 입력의 형태가 void인 main이라는 이름의 함수"
함수는 특성만 정의 했다고 해서 함수가 되는 것은 아니고, 기능을 지녀야 한다.
C언어에서 그 기능은 중괄호 안에 표현이 된다. 그리고 함수의 기능을 정의하고 있는 영역을 가리켜 '함수의 몸체(body)'라 한다.
'출력의 형태'라는 표현은 함수임을 강조할 때 사용이 되며, 실제로는 '반환형(return type)'이라는 표현이 주로 사용된다.
함수 내에 존재하는 문장의 끝에는 세미콜론 문자;을 붙여준다.
printf("Hello world! \n");
소괄호 안에는 함수호출 시 전달할 인자정보를 표현한다. 그리고 C언어는 큰따옴표를 이용해서 문자열을 표현한다.
\n = 이스케이프 시퀀스(escape sequence) = 줄을 바꾸라는(개행) 의미로 사용됨.
printf 함수는 함수호출 시 전달되는 문자열을 모니터에 출력하는 기능을 지님. 기본적으로 제공되는 함수로 '표준함수'라 하고, 이 표준함수들의 모임을 가리켜 '표준 라이브러리'라 한다.
표준함수의 호출을 위해서는 printf 함수와 관련 있는 '헤터파일 선언'이라는 것을 해야 한다.
#include <stdio.h>
이는 stdio.h라는, 확장자가 .h로 끝나는 헤더파일을 포함하라는 의미의 선언임.
표준 라이브러리에는 다양한 표준함수가 존재하기 때문에, 헤더파일도 다양하게 존재함. 그래서 필요에 따라서 여러 개의 헤더파일 선언문을 삽입하기도 함.
return은 함수의 종료와 값의 전달(반환)이라는 두 가지 의미를 지님.
함수를 호출한 영역으로 값을 전달(반환)
현재 실행중인 함수의 종료
주석(comment)은 프로그램 내에 삽입된 메모를 뜻함. 프로그램의 실행결과에 영향을 미치지 않는다.
주석을 다는 것은 프로그램을 분석하는 이들을 배려하는 수단도 되지만, 동시에 프로그램을 구현한 본인 스스로를 배려하는 수단도 된다.
주석은 선택이 아닌 필수다!
블록 단위 주석 시작은 /* 끝은 */
헹 단위 주석 // // 뒤에 등장하는 문장은 주석으로 처리됨.
블록 단위 주석은 중첩될 수 없음. 그러나 행 단위 주성은 블록 단위 주석의 내부에 포함될 수 있다.
프로그램을 구현하다 보면, 다양한 실행결과의 확인을 위해서 일부 코드를 주석처리 하기도 하는데, 이러한 과정에서 위와 같은 형태의 주석이 유용하게 사용되기도 한다.
printf 함수를 이용하면 문자열 이외의 데이터를 다양한 형태로 출력하는 것이 가능하다. 서식문자열을 사용해야 한다.
변수와 연산자.
연산자 - C언어를 이용해서 특정연산을 요구할 때 사용하는 약속된 기호
변수 - 값을 저장할 수 있는 메모리 공간에 붙은 이름, 혹은 메모리 공간 자체를 가리켜 변수라 한다.
따라서 변수를 선언하면, 급 변수의 이름을 통해서 값의 저장 및 참조가 가능하고, 또 저장된 값의 변경도 가능하다.
1999년도에 발표된 C언어의 표준에서는 변수의 선언위치에 아무런 제한을 두지 않고 있다. 하지만 아직도 상당수의 컴파일러가 변수의 선언문이 중괄호의 앞부분에 위치할 것을 요구하고 있으니, 순수하게 C언어로 프로그램을 작성하고자 한다면, 컴파일러가 지원하는 변수선언의 위치에 상관없이 중괄호의 앞부분에 변수를 선언하는 것이 좋다.
변수의 이름을 정할 때에는 변수의 역할에 어울리는, 의미 있는 이름을 지어야 한다.
변수의 자료형
정수냐, 실수냐에 따라서 값이 메모리 공간에 저장되는 방식이 다르기 때문에 크게 두가지로 나뉨.
정수형 변수 - char형, short형, int형, long형
실수형 변수 - float형, double형
증감연산자 - 소괄호의 영향을 받지 않고, 다음 문장으로 넘어가야만 비로소 값의 증가 및 감소가 이뤄진다.
관계연산자 - -> 방향으로 왼쪽을 기준으로 하고 조건을 만족하면 참(true) 1 만족하지않으면 거짓(false) 0 을 반환한다.
논리연산자 &&, ||, ! 전부다 -> 이지만 ! 만 <- 이다. C언어는 0이 아닌 모든 값을 '참(true)으로 간주한다.'
콤마 연산자 - 둘 이상의 변수를 동시에 선언하거나, 둘 이상의 문장을 한 행(line)에 삽입하는 경우에 사용되는 연산자 이다. 둘 이상의 인자를 함수로 전달할 때도 인자의 구분을 목적으로 사용된다.
scanf - 키보드로부터의 정수입력을 위한 scanf 함수의 호출
scanf("%d", &num);
10진 정수형태로 입력받아서 num에 저장하라.
변수 앞에 주소연산자(&)를 붙여야 한다. 입력형태를 다양하게 지정하는 것이 가능하다.
키워드
int. rerurn과 같은 단어들은 이미 그 기능적 의미가 정해져 있다. 이렇듯 기능적 의미가 정해져서 C언어의 문법을 구성하는 단어들을 가리켜 '키워드(keyword)'라 한다. 프로그래머가 다른 용도로 사용할 수 없도록 제한되어 있다.
비트 - 컴퓨터가 표현하는 데이터의 최소단위로서 2진수 값 하나를 저장하 수 있는 메모리의 크기를 뜻하는 단위이다.
바이트 - 비트8개가 모인 단위.
장수와 실수의 표현방식
C언어는 보통 하나의 정수를 4바이트로 표현한다. 그리고 정수의 가장 왼쪽에 존재하는 비트는 '부호비트' 이다.
양수라면 0, 음수라면 1 을 저장하여 부호를 표시한다. 그래서 이 비트를 가리켜 MSB라고 하는데, 이는 Most Significant Bit의 약자로서 가장 중요한 비트라는 뜻을 지닌다.
음의 정수를 표현할땐 MSB를 1로만 바꾸는 것이 아니라 양의 정수에 2의 보수를 취해야 한다.
1의 보수 표현법 -> 비트를 0은 1로 1은 0으로 바꾸면된다
2의 보수 표현법 -> 1의보수에 1을 더하거나 1이 나올때 까지 그대로 적고 1이 나오면 그 이후 비트부터 0은 1로 1 은 0으로 나타낸다.
음의 정수를 양의 정수로 표현할땐 위의 1의 보수 표현법 이나 2의 보수 표현법을 한번 더 한다.
ex) 2진수 11110111을 10진수로 표현하는 방법이다.
첫번째 비트가 1이기 때문에 이 2진수는 음수라는걸 인지하고 변환들어가자.
1단계, 1은 0으로, 0은 1으로 변환한다.(1의보수)
00001000
2단계, 1단계에서 나온 결과에 1을 더하자. (2의보수)
00001001
3단계, 2단계 결과를 10진수화 하여 '-'기호를 붙이자.
-9
--------------------------------------------------
즉, 2진수 11110111 은 10진수로 -9 이다.
역으로 10진수 -9를 2진수로 표현하는 방법이다.
1단계, 9를 8비트로 만들어보자.
00001001
2단계, -9는 음수이기 때문에, 첫번째 부호비트를 1로 바꾸자.
10001001
3단계, 0은 1로, 1은 0으로 바꾸자.(1의보수)
단, 부호비트는 그대로 유지하자.
11110110
4단계, 3단계 결과값에 1을 더하자.
11110111
---------------------------------------------------
즉, 10진수 -9 는 2진수로 11110111 이다.
실수의 표현방식
컴퓨터는 우리가 표현하고자 하는 실수의 값을 정확하게 표햔하는 것이 아니라, 아주 가까운, 문제가 없을 만큼의 근사치를 통해서 실수를 표현하게 된다. 따라서 실수를 표현하는데 있어서 오차가 존재하는 것은 당연한 일이며, 이러한 오차를 가리켜 '부동 소수점 오차'라 한다.
이 '부동 소수점 오차'는 프로그래밍 언어에 상관없이 발생하는 문제이다. 이는 컴퓨터의 실수표현에 대한 한계에서 비롯된 것이므로, C언어가 아닌 다른 프로그래밍 언어에도 존재하는 문제이다.
비트 연산자
비트 단위로 연산을 진행하는 비트 연산자는 주로 하드웨어 관련 프로그래밍에 활용되지만, 그 이외의 영역에서도 사용되어 메모리 공간의 효율성을 높이고 연산의 수를 줄이는 요인이 되기도 한다.
비트 연산자 &, |, ^, ~, <<, >>
대입 연산자 =
산술 연산자 +, -, /, %, *
콤마 연산자 ,
논리 연산자 && || !
복합 대입 연산자 *=, /=, %=, +=, -=, |=, &=, ^=, <<=, >>=
~ -> 1의 보수, 2의 보수가 아니라 그냥 0은 1, 1은 0으로 변환하면 보수가 나옴. 10진수로 바꿔서 표현할땐 2의보 수법을 사용해서 표현.
2의 보수를 취하는 것은 -1을 곱하는 결과로 이어짐. 따라서 음의 정수에 2의 보수를 취하여 그 크기를 확인할 수 있는 것이다.
>>
비트의 열을 왼쪽으로 1칸씩 이동시킬 때마다 정수의 값은 두 배가 된다. 오른쪽으로 1칸씩 이동시킬 때마다 정수의 값은 2로 나누어 진다. 이 사실은 기억하면, 상황에 따라서 곱셈과 나눗셈 연산은 비트의 의동 연산으로 대체할 수 있으며, 이는 성능의 향상으로 이뤄진다. CPU입장에서 곱셈과 나눗셈이 비트의 이동보다 부담스러운 연산이기 때문이다.
>>
양수라면 이동으로 밀려나는 오른쪽의 비트들은 소멸되고 이등으로 인해서 생긴 왼쪽의 빈 자리는 0으로 채워진다. 그러나 num1이 음수라면 이야기는 달라진다. 이 결과는 CPU에 따라서 달라진다. 음의 값을 유지하기 위해서 1을 채우는 CPU, 음의 값 유지에 상관하지 않고 0을 채우는 CPU도 있기 때문이다.