본문 바로가기

프로그래밍 서적/열혈 C 프로그래밍

윤성우님의 열혈 C 프로그래밍...

반응형

처음부터 공부하기 위해, 한달에 한권씩 운영체제부터 공부해보고 있다. 1월에 운영체제를 다 읽지 못해 정리는 못했고, 2월에도 읽으면 늘어질거 같아 우선, 정해놓은 책을 읽고 정리하기로 했다.

 

열혈 C프로그래밍 책은 예전에 사 놓은 책으로, 처음 다 완독했다...거의 1~년만인듯..하다... 반성중...

 

책을 일고 나선, 초보자도 쉽게 이해할 수 있는 설명과 예제가 있는 듯하다. 역시 깊게는 들어가지 않지만 쭉 읽기엔 좋은 책으로 생각이 된다. 필자 역시 공부 목적은 기본 개념 위주이기에, 굳이 필요하지 않다고 생각된 부분은 공부하지 않았다. 공부하면서 재밌는 부분이나 생각나는 부분에 대해 적어본 글이 될 것이다.

 

Chapter01 이것이 C언어다

프로그래밍 언어란 컴퓨터와의 대화에 사용되는 일종의 대화수단이다.

예를 들어, 컴퓨터는 기계어인 0과 1로만 대화가 가능하다. 사람이 쓰는 언어는 기계어가 아니기에, 컴퓨터와 대화 시 중간에 통역을 해주는 것이 필요한데, 이것을 컴파일러(Compiler)라 한다. 따라서, 컴파일러가 이해할 수 있는, 사람도 익히기 쉬운 언어가 프로그래밍 언어가 된다.

즉, 프로그래밍 언어란 사람과 컴파일러가 이해할 수 있는 약속된 형태의 언어를 의미한다.

컴파일러란 프로그래밍 언어로 작성한 프로그램을 컴퓨터가 쉽게 이해할 수 있도록 기계어로 번역하는 역할을 하며, 이 번역하는 일 자체를 가리켜 컴파일(Compile)이라 한다.

 

Chapter02 프로그램의 기본 구성

C프로그램은 함수로 시작해서 함수로 끝난다. 즉, 정해진 순서에 의해 진행되는 함수의 호출이 바로 프로그램의 흐름이 된다.

C언어 역시 main 함수를 먼저 시작하는데, 이는 main함수가 프로그램 시작 시 메모리에 먼저 올라가야만 프로그램이 동작하기 때문이다. 이는 자바에서도 마찬가진데, 자바에서 main 함수가 static으로 쌓여 먼저 실행되는 이유 역시, main함수가 프로그램 시작 시 먼저 메모리에 올라가있어야 내부에 선언한 함수들을 실행시킬 수 있기 때문이다.

함수는 출력형태 함수이름 (입력형태) { 함수의 몸체 } 로 구성되어 있다.

 

Chapter03 변수와 연산자

변수란 임의의 값을 대입할 수 있는 문자를 말하며, C언어에서는 "값을 저장할 수 있는 메모리 공간에 붙은 이름, 혹은 메모리 공간 자체를 가리킨다." 라고 한다. 즉, 변수의 이름을 통해 값의 저장 및 참조가 가능하며, 저장된 값의 변경 역시 가능하다.

변수형 자료형(Data Type)은 정수냐 실수냐에 따라 값이 메모리 공간에 저장 및 참조되는 방식이 다르다. 정수형 변수는 정수의 저장을 목적으로 선언된 변수를 뜻하며, char, short, int, long 형 변수로 나뉜다. 실수형 변수는 소수점 이하의 값을 지니는 실수의 저장을 목적으로 선언된 변수를 뜻하며, float, double 형 변수로 나뉜다.

 

Chapter04 데이터 표현방식의 이해

컴퓨터는 2진수를 기반으로 데이터를 표현하고 연산도 진행한다. 2진수란 두개의 기호, 즉, 0과 1을 이용해 데이터를 표현하는 방식을 의미한다.

컴퓨터가 표현하는 데이터의 최소 단위를 **비트(Bit)**라 하며, 이는 2진수 값 하나를 저장할 수 있는 메모리 크기를 뜻하는 단위이다. 이 비트를 8개 묶음으로 나타내는 것을 **바이트(Byte)**라 한다.

 

Chapter05 상수와 기본 자료형

자료형이란 데이터를 표현하는 기준, 방법을 뜻하며, 변수도, 상수도 자료형이다.

 

Chapter06 printf 함수와 scanf 함수 정리하기

Chapter07 반복실행을 명령하는 반복문

Chapter08 조건에 따른 흐름의 분기

Chapter09 C언어의 핵심! 함수!

변수의 선언 위치에 따라 지역변수와 전역변수로 크게 나눈다. 두 차이점은 메모리상에 존재하는 기간과 변수에 접근할 수 있는 범위이다.

함수 내에만 존재 및 접근할 수 있는 지역변수(Local Variable)은 중괄호 내에서 선언되는 변수이다. 즉, 중괄호를 벗어나면 메모리에서 자동으로 소멸된다. 함수의 매개변수 역시 지역변수라 할 수 있다.

전역변수는 다음과 같은 특징이 있다.

프로그램의 시작과 동시에 메모리 공간에 할당되어 종료 시까지 존재
별도의 값으로 초기화하지 않으면 0으로 초기화
프로그램 전체 영역 어디서든 접근이 가능

이러한 특징이 있으며, 지역변수와 네이밍이 같을 경우, 지역 변수로의 접근이 먼저 이뤄진다.

지역변수에 static 이 붙는다면, 아래와 같은 특성이 존재한다.

선언된 함수 내에서만 접근이 가능(지역변수 특성)
딱 1회 초기화되고 프로그램 종료 시까지 메모리 공간에 존재(전역변수 특성)
자바에서의 전역 변수와 static 을 붙인 변수와의 차이는, static 변수는 class의 인스턴스화할 필요 없이 className.변수이름으로 접근이 가능하고, 전역 변수는 class의 인스턴스화가 반드시 이뤄진 다음에 접근이 가능하다. 이 차이점은 Java는 class로 구성되어있기 때문이라 본인은 생각한다.

 

Chapter11 1차원 배열

배열이란 다수의 데이터를 저장하고 처리하기 위해 사용하는 것이다. 배열은 배열의 크기를 할당 시, 그만큼의 메모리를 할당하고 데이터를 넣어준다. 즉, int arr[5] 는 int 자료형인 4바이트 크기가 5개 있으므로, 20바이트 크기의 배열을 설정해주는데, int arr[5] = {1, 2}; 라고 선언해도, 20바이트는 선언이 되어 있는 상태로, 나머지는 빈 값인 0이 들어가있다.

 

Chapter12 포인터의 이해

변수를 선언하면, 메모리 공간에 주소값으로 할당이 되는데, 예를 들어, int num = 7; 은, "int형 변수 num은 0x12ff76번지에 할당되어 있다." 라고 할 수 있다. 주소값인 0x12ff76 역시 정수이며, 이 값을 저장하기 위해 마련된 변수가 바로 포인터 변수이다. 즉, 포인터 변수란 메모리의 주소 값을 저장하기 위한 변수라 할 수 있다. (포인터는 변수 형태의 포인터상수 형태의 포인터를 어우르는 표현)

int *pnum = # 으로 식을 만들 수 있는데, 여기서 *는 포인터 변수를 선언하기 위해 필요한 연산자이며, &는 변수 num의 주소값을 가져오는 연산자이다. (32비트 시스템에선 주소 값을 32비트로 표현하기에 포인터 변수의 크기는 4바이트이지만, 64비트 시스템에서는 주소 값을 64비트로 표현하기에 크기 역시 8바이트가 된다.)

 

Chapter13 포인터와 배열! 함께 이해하기

배열의 이름은 값을 바꿀 수 없는 상수 형태의 포인터이자, 포인터 상수라 한다. 즉, 배열의 이름은 배열의 시작 주소 값을 의미하며, 그 형태는 값의 저장이 불가능한 상수이다. 포인터 변수와의 공통점은, 이름이 존재하며, 저장하는 것은 메모리의 주소 값이다. 차이점은 주소 값의 변경은 포인터 변수는 가능하며, 배열의 이름은 불가능하다.

ex) int arr[3] = {1, 2, 3}; → arr = 0x0012ff50, &arr[0] = 0x0012ff50, &arr[1] = 0x0012ff54, &arr[2] = 0x0012ff58 이다.

 

Chapter14 포인터와 함수에 대한 이해

인자 전달의 기본 방식은 값의 복사이다. 이 말은 함수 호출 시 전달되는 인자의 값은 매개변수에 복사가 된다는 뜻이다. 하지만, 배열을 인자로 전달하고 싶다면? 배열은 위에 얘기했듯, 포인터 상수이다. 즉, 매개변수로 들어오는 배열은 이 아닌, 주소 값을 전달한다고 할 수 있다. 여기가 중요하다. 단순히 값을 전달하는 형태의 함수 호출은 다른 말로, Call-by-value라 하고, 메모리 접근에 사용되는 주소 값을 전달하는 형태의 함수 호출은 Call-by-reference라고 한다. 이 두 개념은 반드시 이해하고 넘어가자!

위 개념을 이해했으면, scanf("%d", &num); 에서 &를 사용하는 이유를 유추할 수 있다. 답은 변수 num의 주소 값을 scanf 함수에 전달하기 위해서다. 즉, 주소 값을 전달해야 메모리에 접근해 변수를 할당할 수 있기 때문이다. 하지만 num이 배열이라면 &를 붙여줘야 할까? 아니다. 배열은 그 자체로 주소 값이기에, scanf("%s", num) 형태로 나타낼 수 있다.

 

Chapter15~20 은 그닥 중요하다 생각 안해 패스...

Chapter21 문자와 문자열 관련 함수

스트림(Stream)이란 데이터의 이동 경로를 의미한다. 프로그램 상에서 모니터로 문자열을 출력할 수 있는 이유는, 프로그램 상에서 모니터와 키보드를 대상으로 데이터를 입출력하기 위해 이들을 연결시켜주는 다리 역할을 하는 매개체인 스트림(Stream)이 존재하기 때문이다. 그렇다면 스트림의 정체는? OS에서 제공하는 SW적인 가상의 다리이며, OS는 외부 장치와 프로그램과의 데이터 송수신의 도구가 되는 스트림을 제공한다 볼 수 있다.

따라서 스트림은 한 방향으로 흐르는 데이터의 흐름이자, 단방향 데이터 전송이 이뤄진다고 볼 수 있다.

데이터 입출력 시 해당 데이터들은 OS가 제공하는 메모리버퍼를 중간에 통과하게 된다. 메모리 버퍼란 무엇일까? 데이터를 임시로 모아두는, 저장하는 메모리 공간이다. 그럼 우리가 흔히 듣는 버퍼링(Buffering)은? 데이터를 한데 묶어서 이동시키는 것이 하나하나 이동하는 것보다 효율적이기에, 한데 묶기 위한 공간인 버퍼가 필요하고, 이를 버퍼링이라 한다.

 

Chapter 22~24 패스

Chapter25 메모리 관리와 메모리의 할당

프로그램 실행 시 운영체제(OS)에 의해서 마련되는 메모리의 구조는 4개의 영역(코드 영역, 데이터 영역, 힙 영역, 스택 영역)으로 구분된다. 이렇게 4개의 영역으로 나눈 이유는, 유사한 성향의 데이터를 묶어 저장함으로써 관리가 용이해지고 접근 속도도 향상되기 때문이다. 각 영역에 대한 설명은 아래와 같다.

  • 코드 영역(Code Area) : 실행할 프로그램의 코드가 저장되는 메모리 공간으로, CPU는 여기에 저장된 명령문들을 하나씩 가져가 실행한다.
  • 데이터 영역(Data Area) : 전역변수와 static 변수가 할당되어, 프로그램 시작부터 끝까지 남아있다.
  • 스택 영역(Stack Area) : 지역변수와 매개변수가 할당되어, 함수의 호출 시 생성, 종료 시 소멸된다.
  • 힙 영역(Heap Area) : 프로그래머가 원하는 시점에 변수를 할당하고 소멸하도록 지원하는 영역.

 

Chapter26 매그로와 선행처리기(Preprocessor)

일반적으로 실행 파일은 선행처리, 컴파일, 링커 과정을 거친다.

소스 파일 → 선행처리기 → 선행 처리를 거친 소스 파일 → 컴파일러 → 오브젝트 파일 → 링커 → 실행파일

로 만들어지는데, 선행처리기에서는 삽입해 놓은 선행처리 명령문대로 소스 코드의 일부를 수정, 즉, 단순 치환(substitution)한다. 즉, #define PI 3.14 라고 정의되어 있으면, PI를 만날때마다 3.14로 치환된 코드가 생성된다고 볼 수 있다. 그렇게 처리된 소스 파일은 컴파일러에 의해 컴파일 과정을 거치며, 바이너리 데이터로 이뤄진 오브젝트 파일이 생성된다.

반응형