본문 바로가기

개발 공부/컴퓨터시스템 (CSAPP)

1. 컴퓨터 시스템으로의 여행 [1.1~1.3]

1.1 정보는 비트와 컨텍스트로 이루어진다.

개발자가 작성한 코드 = 소스프로그램 (or 소스파일)는 0 또는 1로 표시되는 비트들로 이루어진다.

1바이트 = 8비트 단위로 구성되며, 각 바이트는 프로그램의 텍스트 문자를 나타낸다.

텍스트 문자는 아스키 표준을 사용하여 표시하는데, 각 문자를 바이트 길이의 정수 값으로 표현한다.

아래 표에서

Dec은 Decimal 을 의미하는 10진수 값이고, Char은 우리가 보는 문자 값이다.

Hx Oct는 궁금해서 찾아보니 Hexadecimal (16진수) , Octal (8진수) 라고 한다.

컬러 헥스값으로 알려줘~ 할 때 Hx = Hex 였던 것!

 

 

출처 : 나무위키

 

재밌던 것은 개행문자라고 하는 \n 은 백슬래쉬+n이 아니고 아스키코드 10진수 10에 해당한다는 것이다.

표에서는 new line 으로 되어있다.

만약  아래 코드를 [hello.c] 파일로 저장했다면, 아스키코드로만 이루어진 파일이라 텍스트 파일이라 칭한다.

다른 모든 파일들은 바이너리 파일이라고 한다.

#include <studio.h>

int main()
{
	printf("hello, world\n");
    return 0;
}

위 코드에서 우리 눈에 보이는 헬로월드 뒤 \n은 아스키코드 10진수 값으로  \ → 92 , n → 110 으로 저장된다.

실제 엔터가 실행되는 곳이 10 으로 저장되는 것.

 

중요한 것은 컴퓨터 내의 숫자 표현이 내가 아는 정수, 실수와 같은 개념이 아니기 때문에 이에 대해 학습해야 한다.

 

C언어의 tmi
- 개발자 한 명이 설계를 관리해서 깔끔하고 일관된 설계가 가능했다.
- 유닉스 운영체제를 위해 개발된 언어이다. 
- C언어의 포인터는 혼란을 야기하고 프로그래밍 에러의 원인이 된다. (ㅋㅋㅋ)

 

1.2  프로그램은 다른 프로그램에 의해 다른 형태로 번역된다.

 

 

gcc 컴파일러 드라이버는 소스파일 hello.c 를 읽어 실행파일인 hello 로 번역한다.

컴파일러 드라이버는 유닉스 시스템에서 다음과 같이 소스파일에서 목적파일로 번역하는 것.

linux> gcc -o hello hello.c

 

위에서 C언어로 작성한 텍스트 파일은 결국은 컴퓨터가 알아먹을 수 있도록 네 단계를 거친다.

[전처리기, 컴파일러, 어셈블러, 링커] 이 4개의 프로그램 합쳐 이를 컴파일 시스템이라고 한다.

 

1. 전처리 단계:

전처리기(cpp)는 #로 시작하는 명령어에 따라 C 프로그램을 수정한다.

헤더파일인 studio.h를 프로그램 문장에 직접 삽입하라고 명령하고, 일반적으로 .i로 끝나는 새로운 C 프로그램이 생성된다.


2. 컴파일 단계:

컴파일러(cc1)은 hello.i를 hello.s로 번역하는데 이 파일에 어셈블리어 프로그램이 저장된다.

main함수의 정의를 저수준 기계어 명령어로 텍스트 형태로 나타낸다.

 

--------여기까지는 텍스트 파일 이후 단계부터는 바이너리 파일로 변환-------

 

3. 어셈블리 단계:

어셈블러(as)는 hello.s를 기계어 인스트럭션으로 번역하고,

재배치가능 목적프로그램의 형태로 묶어서 hello.o 라는 목적파일에 그 결과를 저장한다.

main 함수를 인스트럭션들을 인코딩하기 위한 17바이트를 포함하는 바이너리 파일이다.

hello.o 를 텍스트 편집기로 열어보면 쓰레기같은 데이터로 보인다고 한다.


4. 링크 단계:

링커(ld)

printf 함수는 이미 컴파일된 별도의 목적파일인 printf.o 에 들어있으며, 이 파일이 hello.o 파일과 어떤 형태로든 결합되어야 한다.

링커 프로그램이 바로 이 결합을 수행한다.

결과적으로 hello파일은 실행가능 목적파일 (=실행파일)로 메모리에 적재되어 시스템에 의해 실행된다.

 

 

1.3 컴파일 시스템이 어떻게 동작하는지 이해하는 것은 중요하다.

  • 프로그램 성능 최적화 :
    • switch문은 if-else문을 연속해서 사용하는 것보다 언제나 더 효율적일까?
    • while 루프는 for 루프보다 더 효율적일까?
    • 포인터 참조가 인덱스보다 더 효율적인가?

이러한 질문들에 대답하고, 효율적으로 프로그래밍 하기 위해서 이해가 필요하다.

  • 링크 에러 이해하기 :
    • 필자의 말에 따르면 링커의 동작과 관련한 에러가 가장 당혹스러웠다고 한다.
    • 큰 규모의 소프트웨어일수록 더욱 그렇다.
  • 보안 약점 피하기 :
    • 버퍼 오버플로우 취약성이 인터넷, 네트워크의 약점이다.
    • 신뢰할 수 없는 곳에서 획득한 데이터의 양과 형태를 주의깊게 제한하지 않아서 그렇다.
    • 안전한 프로그래밍의 첫 단계는 프로그램 스택에 데이터와 제어 정보가 저장되는 방식에 따라 생겨나는 영향을 이해하는 것이다.

 

컴퓨터 시스템

저자 : Randal E. Bryan , David R. O'Hallaron 저자(글) · 김형신 번역