boostcource
모두를 위한 컴퓨터 과학 (CS50 2019) : David J. Malan
www.boostcourse.org/cs11
실습환경 : CS50 Sandbox & CS50 IDE
컴파일링
처음 C언어를 공부하면서 컴파일에 대해 짧게 짚고 넘어갔었다. (참고)
컴파일(compile)은
우리가 작성한 코드(소스 코드)를
컴퓨터가 이해할 수 있는 언어의 코드(머신 코드)로 번역해주는 과정이다.
컴파일링을 수행하는 프로그램을 컴파일러(compiler)라고 칭하며
CS50 Sandbox 로 실습하는 중에는 컴파일러로 clang 을 사용했다.
예제로 a.c 라는 프로그램을 작성하고나서 hello로 컴파일 하고 싶다면 아래와 같이 작성할 수 있다.
// 컴파일
$ clang -o hello a.c
// 실행
$ ./hello
또한 CS50 라이브러리를 사용해서 프로그램을 만들었다면
컴파일 할 때에도 clang에 -lcs50 이라는 명령을 붙여야 했다.
$ clang -o hello a.c -lcs50
이 명령어는 clang에게 CS50 라이브러리(cs50.h)에 있는 모든 0과 1들을 연결하라는 의미다.
컴파일링의 4단계
위와 같은 컴파일 과정에서 정확히 어떤 일이 일어나는지 알아보기 위해서는
컴파일 전체 과정을 네 단계로 나누어볼 수 있다.
Precompile - Compile - Assemble - Link
전처리 (Precompile)
소스 코드 👉 전처리한 소스 코드
실질적인 컴파일이 이루어지기 이전에 무언가를 실행하라고 전처리기에게 알려주는 단계이다.
여기에서 무언가는? C 소스 코드 맨 위에서 #으로 시작하는 내용이다.
#include <stdio.h>
#include <cs50.h>
예를 들어 위와 같이 #include가 작성되어 있다면
전처리기에게 stdio.h 와 cs50.h 의 내용을 포함시키라고 알려준다.
전처리기는 C 소스 코드 형태의 새로운 파일을 생성하는데
그 안에 stdio.h 와 cs50.h 의 내용이 안에 들어가도록 만들게 된다.
컴파일 (Compile)
전처리한 소스 코드 👉 어셈블리 코드
전처리기가 전처리한 소스 코드를 생성하고 나면
컴파일러를 이용해서 C 소스 코드를 어셈블리어라는 저수준 프로그래밍 언어로 번역한다.
어셈블리는 C와 비교해 연산의 종류는 훨씬 적지만, 이 연산들을 여러개 함께 사용해 C에서 할 수 있는 모든 것들을 수행할 수 있다.
아직 컴퓨터가 완전히 이해할 수는 없지만, 소스코드보다는 나은 형태라고 생각하면 된다.
컴파일이라는 용어는 이렇게
이렇게 소스 코드 -> 머신 코드로 변환하는 전체 과정을 칭하기도,
전처리한 소스 코드 -> 어셈블리 코드로 변환하는 구체적인 단계를 말하기도 한다.
어셈블 (Assemble)
어셈블리 코드 👉 오브젝트 코드
위 컴파일 과정으로 생성된 어셈블리 코드를
이번에는 어셈블러를 이용해서 오브젝트 코드로 변환시킨다.
오브젝트 코드는 0과 1로 이루어진 명령어들로 구성된다.
이를 통해 컴퓨터의 중앙처리장치(CPU)가 프로그램을 어떻게 수행해야 하는지 알려준다.
만일 소스 코드에서 오브젝트 코드로 컴파일 되어야 할 파일이 1개라면 컴파일 작업은 종료된다.
그렇지 않다면 아래의 링크 단계로 넘어간다.
링크 (Link)
다수의 오브젝트 코드 파일 👉 한 개의 오브젝트 코드 파일
만일 프로그램이 여러 개의 파일로 이루어져 있어서
(math.h, cs50.h 와 같은 라이브러리들도 여러 개의 파일에 해당함)
하나의 오브젝트 코드로 합치는 과정이 필요하다면 링크 단계가 필요하다.
프로그램이 1개의 파일 뿐이라면 링크 단계는 필요하지 않다.
예시로, 컴파일 하는 동안 CS50 라이브러리를 링크하면
오브젝트 코드가 GetInt() 나 GetString() 같은 함수를 어떻게 실행할 지 알 수 있게 된다.
이렇게 네 단계를 거치면 최종적으로 실행 가능한 파일이 완성된다!
디버깅
버그와 디버깅
버그(bug)는 코드에 들어있는 오류를 말한다.
컴퓨터 과학자인 그레이스 호퍼(Grace Hopper)가 'Mark 2'라는 컴퓨터 시스템을 만들던 도중 계속 오작동이 일어나 원인을 조사했는데, 시스템 안에 죽은 나방이 들어가있던 것을 발견했다.
이 나방을 일지에 붙이며 "First actual case of bug being found"(버그가 발견된 최초의 사례) 라고 기록해두었는데
이를 계기로 컴퓨터 프로그램이나 시스템의 오작동의 원인이 되는 것을 버그라고 말하기 시작했다고 한다.
정리하자면, 버그는 의도하지 않은 프로그램 내 실수를 의미한다.
디버깅(debugging)은 코드에 있는 버그를 식별하고 고치는 과정이다.
위 유래에서 나방을 발견해 제거한 것을 첫 번째 디버깅이라고 말할 수 있겠다.
프로그래머는 디버거 라는 프로그램을 사용해서 디버깅을 하게 된다.
디버깅의 기본
프로그램은 일반적으로 인간보다 훨씬 빠른 연산을 하기 때문에
디버거는 프로그래머가 버그를 찾을 수 있도록 프로그램을 특정 행에서 멈출 수 있게 해준다.
이 멈추는 특정 지점을 중지점(break point)이라고 하며, 그 지점에서 무슨 일이 일어났는지를 볼 수 있다.
즉, 프로그램이 내리는 모든 결정들을 단계별로 따라갈 수 있게 해준다.
help50
실습환경인 CS50 IDE를 이용해서 아래와 같은 예시를 입력해보자.
int main(void){
printf("hello, world\n");
}
make 를 이용해 컴파일을 하면 다음과 같은 에러 메세지가 발생한다.
빨간 글씨로 적힌 error 다음에 나오는 "implictly declaring library function 'printf' .. " 부분이 주 에러 내용이다.
여기에서 help50 프로그램을 사용하면 컴파일 시 생기는 오류를 해석해준다
$ help50 make 소스파일명
"implictly declaring library function 'printf' .. " 부분을 읽고
"Did you forget to #include <stdio.h> atop your file?" 라는 질문을 던져주는 모습이다.
printf 함수를 사용하기 위한 stdio.h 라이브러리를 상단에 삽입했는지에 대한 체크로 적절한 확인이라고 할 수 있다.
printf
이번에는 위의 예시와 조금 다르게
프로그램을 사용해서 해결할 수 없는 문제인 경우다.
한 프로그래머가 반복문을 이용해서 # 을 10번 찍어내는 코드를 아래와 같이 작성했다.
#include <stdio.h>
int main(void){
for(int i=0; i<=10; i++){
printf("#\n");
}
}
하지만 위 코드는 기대와 달리 # 을 11번 찍어낸다.
위는 잘못된 기호나 명령어를 이용해서 문법적으로 나타는 버그가 아닌 논리적 오류가 난 예시다.
문법적인 버그가 아니기 때문에 위에서 배운 help50을 이용해서 교정할 수도 없었다.
이 경우 우리가 사용할 수 있는 가장 간단한 디버거는 printf 다.
반복문에 i의 값을 확인하는 printf문을 추가로 작성해서 원인을 파악할 수 있다.
#include <stdio.h>
int main(void){
for(int i = 0; i <= 10; i++){
printf("i is now %i", i);
printf("#\n");
}
}
// ? i is now 0#
// ? i is now 1#
// ? i is now 2#
// ? i is now 3#
// ? i is now 4#
// ? i is now 5#
// ? i is now 6#
// ? i is now 7#
// ? i is now 8#
// ? i is now 9#
// ? i is now 10#
확인해보니 i가 0일 때부터 10일 때까지로 작성했기 때문.
원하는 코드를 작성하려면 for(int i=0; i<10; i++) 또는 for(int i=1; i<=10; i++) 해주어야 한다는 것을 알 수 있다.
debug50
이쯤에서 CS50 IDE을 새로 소개한다.
IDE는 통합 개발 환경(Integrated Development Environment)이며
CS50 IDE는 CS50 Sandbox 와 큰 차이는 없지만
디버깅 도구와 같은 몇 가지 추가 기능을 갖고 있는 프로그래밍 환경이다.
CS50 IDE에서는 debug50 이라는 디버거를 사용할 수 있다.
(참고로 강의 막바지에서는 CS50 프로그램들에 의존하지 않을 예정)
왼쪽의 행을 나타내는 숫자 옆 여백을 클릭하면 빨간색 원이 생기는데 여기가 중지점이다.
중지점을 설정한 뒤 make로 컴파일하고, 그 다음 debug50을 이용해 디버깅을 시도해보자
$ debug50 파일명
노란색으로 강조된 줄은 실제로 실행될 첫 줄이자, 디버거가 프로그램을 이 지점으로 멈췄다는 표시이다.
그리고 우측상단 아이콘 중에서
재생버튼은 프로그램을 끝까지 실행시키는 아이콘,
그 좌측, 빨간색으로 표시한 두번째 아이콘은 다음 한 단계만 실행시키는 아이콘이다.
해당 아이콘을 클릭하면
프로그램이 실행되는 위치와 함께 변수(variable) i의 값(value)이 어떻게 변화하는지를, 그리고 printf가 실행되는 과정이 어떤지까지 상세하게 모두 볼 수 있다.
이렇게 디버거를 이용한다면 다양한 분기점에 대해 확인이 가능하기 때문에
변수나 함수의 개수가 많은 경우, 그리고 조건문, 재귀함수, 사용자 지정함수 등으로 구문이 복잡한 경우 등에서 크게 도움을 받을 수 있다.
CS50 IDE 추가 기능
debug50 이외에 CS50 IDE에서 이용할 수 있는 다른 기능들을 추가로 소개해본다.
주로 규모가 큰 프로그램을 다수와 작성할 때 사용할 수 있는 기능들이다.
check50
과제를 잘 수행했는지에 대한 검사를 지원하는 자동 검사 프로그램이다.
코드의 정확성을 관리하기 위한 수단이다.
실제로 많은 사람들이 함께 작업하는 환경에서 많은 도움이 되는데
여러 사람들이 각자 한 부분을 맡아 코드를 작성할 때,
각자 수정한 코드가 전체 프로그램의 정확성을 해치지 않는지 쉽게 확인할 수 있기 때문이다.
style50
코드가 심미적으로 잘 작성되어 있는지를 검사해주는 프로그램이다.
코드의 디자인을 관리하기 위한 수단이다.
공백의 수나 줄바꿈 같이 코드의 실행에 직접적으로 영향을 주지는 않는 부분에 관한 것으로
통일성 있는 코드를 작성해서 여러 사람들이 읽고 이해하는데 도움주기 위함이다.
이를 이용함으로써 코드를 작성하는 인원들끼리의 이해 비용을 최소화할 수 있다.
참고로 각 코드의 디자인이 다른 예시는 아래와 같다
for(let i = 0; i < 10; i++)
{
printf("#\n");
}
for(let i=0; i<10; i++){
printf("#\n");
}
for(let i=0; i<10; i++){ printf("#\n"); }
복습 퀴즈
Q1. 컴파일링 4단계에 해당하지 않는 것은?
① 전처리(preprocessing)
② 어셈블링(assembling)
③ 링킹(linking)
④ 디버깅(debugging)
답: ④ 디버깅(debugging)
디버깅은 소스코드 내에 존재하는 오류를 해결하기 위한 작업이며, 컴파일링 단계에 속하지 않는다.
'CS > CS50' 카테고리의 다른 글
[CS][CS50] 배열 - 문자열과 배열 / 문자열의 활용 (0) | 2023.06.08 |
---|---|
[CS][CS50] 배열 - 배열 (0) | 2023.05.29 |
[CS][CS50] C언어 - 하드웨어의 한계 (2) | 2023.05.26 |
[CS][CS50] C언어 - 사용자 정의 함수, 중첩 루프 (0) | 2023.05.24 |
[CS][CS50] C언어 - 자료형, 형식 지정자, 연산자 (0) | 2023.05.09 |