main 이라는 함수는 프로그램의 시작점으로써 실행 버튼을 클릭하는 것과 같다.
printf는 출력을 담당하는 함수이다.
printf 함수를 사용하기 위해서는 stdio.h 라이브러리가 필요하다.
정확히 말하면 stdio.h는 헤더 파일로 C언어로 작성되어 있으며 파일명이 .h로 끝나는 파일이다.
이 파일에는 printf 함수의 프로토타입이 있어서 Clang 컴파일러가 프로그램을 컴파일할때 printf가 무엇인지 알려주는 역할을 한다.
코드를 clang hello.c로 컴파일하고 ./a.out 명령으로 프로그램을 실행할 때
이 과정은 컴퓨터가 이해하는 0과 1로 가득찬 파일 a.out을 생성하여 실행 가능하게 한다.
만약 a.out과 다른 이름(hello)으로 컴파일을 하고 싶다면 아래와 같이 명령행 인자를 추가해야줘야 한다.
clang -o hello hello.c
나는 stdio.h 라이브러리를 제외하고도 수업에서 제공되는 CS50 라이브러리를 사용했다.
이 처럼 CS50 라이브러리를 사용한 프로그램을 컴파일 할때는 clang에 또 하나의 프로그램(-lcs50)이 필요했다.
그래야 clang이 실행되었다.
clang -o hello hello.c -lcs50
위 코드는 clang에게 CS50 라이브러리에 있는 모든 0과 1들을 여기에 연결하라는 의미이다.
위 방법이 약간 귀찮지? ㅎ
make 프로그램을 이용하면 이 모든 컴파일 과정을 자동으로 처리할 수 있다!
make나 clang을 사용해서 프로그램을 실행할 때 아래 네 개의 단계를 거친다.
- 전처리
- 컴파일링
- 어셈블링
- 링킹
명령어를 실핼하면 다음과 같은 실행이 일어난다.
전처리(Precompile)
컴파일의 전체 과정은 네 단계로 나누어볼 수 있다. 그 중 첫 번째 단계는 전처리인데, 전처리기에 의해 수행된다.
# 으로 시작되는 C 소스 코드는 전처리기에게 실질적인 컴파일이 이루어지기 전에 무언가를 실행하라고 알려준다.
예를 들어, #include는 전처리기에게 다른 파일의 내용을 포함시키라고 알려준다.
프로그램의 소스 코드에 #include 와 같은 줄을 포함하면,
전처리기는 새로운 파일을 생성하는데 이 파일은 여전히 C 소스 코드 형태이고 stdio.h 파일의 내용이 #include 부분에 포함된다.
컴파일(Compile)
전처리기가 전처리한 소스 코드를 생성하고 나면 그 다음 단계는 컴파일이다.
컴파일러라고 불리는 프로그램은 C 코드를 어셈블리어라는 저수준 프로그래밍 언어로 컴파일한다.
어셈블리는 C보다 연산의 종류가 훨씬 적지만, 여러 연산들이 함께 사용되면 C에서 할 수 있는 모든 것들을 수행할 수 있다.
C 코드를 어셈블리 코드로 변환시켜줌으로써 컴파일러는 컴퓨터가 이해할 수 있는 언어와 최대한 가까운 프로그램으로 만들어 준다.
컴파일이라는 용어는 소스 코드에서 오브젝트 코드로 변환하는 전체 과정을 통틀어 일컫기도 하지만,
구체적으로 전처리한 소스 코드를 어셈블리 코드로 변환시키는 단계를 말하기도 한다.
어셈블(Assemble)
소스 코드가 어셈블리 코드로 변환되면, 다음 단계인 어셈블 단계로 어셈블리 코드를 오브젝트 코드로 변환시키는 것이다.
컴퓨터의 중앙처리장치가 프로그램을 어떻게 수행해야 하는지 알 수 있는 명령어 형태인 연속된 0과 1들로 바꿔주는 작업이다.
이 변환작업은 어셈블러라는 프로그램이 수행한다.
소스 코드에서 오브젝트 코드로 컴파일 되어야 할 파일이 딱 한 개라면, 컴파일 작업은 여기서 끝이 난다.
그러나 그렇지 않은 경우에는 링크라 불리는 단계가 추가된다.
링크(Link)
만약 프로그램이 (math.h나 cs50.h와 같은 라이브러리를 포함해)
여러 개의 파일로 이루어져 있어 하나의 오브젝트 파일로 합쳐져야 한다면 링크라는 컴파일의 마지막 단계가 필요하다.
링커는 여러 개의 다른 오브젝트 코드 파일을 실행 가능한 하나의 오브젝트 코드 파일로 합쳐준다.
예를 들어, 컴파일을 하는 동안에 CS50 라이브러리를 링크하면 오브젝트 코드는 GetInt()나 GetString() 같은 함수를
어떻게 실행할 지 알 수 있게 된다.
이 네 단계를 거치면 최종적으로 실행 가능한 파일이 완성된다.
버그와 디버깅
버그(bug)는 코드에 들어있는 오류이다. 버그로 인해 프로그램의 실행에 실패하거나 프로그래머가 원하는 대로 동작하지 않게 된다.
디버깅(debugging)은 코드에 있는 버그를 식별하고 고치는 과정이다.
프로그래머는 디버거라고 불리는 프로그램을 사용하여 디버깅을 하게 된다.
디버깅의 기본
프로그램은 일반적으로 인간보다 훨씬 빠르게 연산을 수행한다. 그래서 프로그램을 실행시켜보는 것만으로는 무엇이 잘못됐는지 찾아내기 어렵다. 디버거는 프로그램을 특정 행에서 멈출 수 있게 해주기 때문에 버그를 찾는데 도움이 된다.
프로그래머는 멈춰진 그 지점에서 무슨 일이 일어나는지 볼 수 있다. 프로그램이 멈추는 특정 지점을 중지점이라고 한다.
또한 프로그래머가 프로그램을 한번에 한 행씩 실행할 수 있게 해준다.
이로써 프로그래머는 프로그램이 내리는 모든 결정들을 단계별로 따라갈 수 있게 된다.
help50라는 코드를 실행한다고 가정하자.
int main(void)
{
printf("hello, world\n");
}
make 프로그램을 이용하여 컴파일해보면 “implicitly declaring library function 'printf'” 이라는 에러 메시지가 나타난다.
이런 에러 메시지를 이해하기 힘들다면, help50 프로그램을 사용해보자.
아래와 같이 make 앞에 help50 을 붙여서 실행하면 다시 컴파일시 생기는 오류를 해석해준다.
help50 make 파일이름
위 문제의 원인은 printf 함수를 사용하기 위해서 stdio.h 라이브러리를 포함해야 한다는 것
printf
하지만 이렇게 프로그램을 사용해서 해결할 수 없는 문제도 있다.
아래 코드는 #을 10개 출력하기 위해 작성한 것이다.
#include <stdio.h>
int main(void)
{
for (int i = 0; i <= 10; i++)
{
printf("#\n");
}
}
이 코드를 컴파일 하고 실행해보면 에러는 발생하지 않지만,
의도와는 다르게 #이 11개나 출력되는 것을 확인할 수 있다.
디버깅의 다른 방법으로 직접 의심이 가는 변수를 출력해서 확인해 볼 수 있다.
아래와 같이 변수 i를 출력해보면
#include <stdio.h>
int main(void)
{
for (int i = 0; i <= 10; i++)
{
printf("i is now %i: ", i);
printf("#\n");
}
}
그 결과 i가 0에서 시작하기 때문에 for 루프의 i <= 10 이라는 조건은 실제로 11번 만족한다는 사실을 알 수 있다.
따라서 이를 i < 10 으로 수정해주면 우리 의도대로 #이 10번 출력될 것이다.
debug50
CS50 IDE를 사용하면 debug50이라는 프로그램도 사용할 수 있다.
아래와 같이 소스 코드에 직접 브레이크포인트를 지정하고 소스파일을 컴파일한 후에 “debug50 파일명” 으로 실행하면,
오른쪽 패널을 통해 변수의 값을 확인하거나 브레이크포인트부터 한 줄씩 코드를 실행해 볼 수 있습니다.
디버깅 종료를 위해서는 Ctrl + c를 누르면 됩니다.
이는 CS50에서 제공한 통합개발환경이고 보통은 이클립스, 비주얼 스튜디오 같은 통합개발환경을 이용한다 참고바람*
나는 공부를 위해 디버깅 과정에서 이를 이용해보았다.
코드의 디자인
check50
check50 프로그램을 이용하면 과제를 잘 수행했는데 자동으로 검사할 수 있다.
이 프로그램은 cs50 강의를 공부하며 제공받은 프로그램이다!
실제 많은 사람들이 함께 작업하는 환경에서 이와 같은 자동 검사 프로그램은 많은 도움이 된다.
여러 사람들이 각자 한 부분을 맡아 코드를 작성할 때 각자가 수정한 코드가 전체 프로그램의 정확성을 해치지 않는지 쉽게 확인할 수 있기 때문이다.
style50
style50 프로그램을 이용하면 코드가 심미적으로 잘 작성되어 있는지 검사할 수 있다. (이 프로그램 역시 CS50에서 제공 받은 프로그램)
공백의 수나 줄바꿈과 같은 것들은 코드의 실행에 직접적으로 영향을 주지는 않지만,
코드를 작성하는 사람들이 코드를 읽고 이해하는데 영향을 주기 때문이다.
예를 들어 아래와 같이 for 루프를 작성할 때도 사람에 따라 여러 방식으로 작성할 수 있다.
for (int i = 0; i <= 10; i++)
{
printf("#\n");
}
for (int i = 0; i <= 10; i++){
printf("#\n");
}
for (int i = 0; i <= 10; i++){ printf("#\n"); }
많은 회사들은 사내에서 코드를 작성할 때 특정한 스타일 가이드를 따르도록 한다.
여러 사람들이 코드를 작성하기 때문에 서로 불필요한 오해를 없애고, 코드를 이해하는 데 드는 비용을 최소화하기 때문이다.
때로는 코드에 포함된 오류를 해결할 때 해결을 도와줄 프로그램들이 존재하지 않거나,
있다 하더라도 디버깅에 큰 도움이 안 될 수 도 있다. 이 때는 먼저 한숨 돌리고 직접 곰곰히 생각해보는 수 밖에 없다.
한가지 유명한 방법으로 ‘고무 오리’와 같이 무언가 대상이 되는 물체를 앞에 두고,
내가 작성한 코드를 한 줄 한 줄 말로 설명해주는 과정을 거쳐볼 수 있다.
이를 통해 미처 놓치고 있었던 논리적 오류를 찾아낼 수도 있다.
'공부 STUDY > CS' 카테고리의 다른 글
CS50 | 알고리즘 - 검색 알고리즘, 알고리즘 표기법, 선형 검색 (0) | 2022.06.25 |
---|---|
CS50 | 배열 Array (2) - 배열, 문자열과 배열, 문자열의 활용, 명령행 인자 (0) | 2022.06.23 |
CS50 | C 언어 - (2) (0) | 2022.06.21 |
CS50 | C 언어 - (1) (0) | 2022.06.21 |
1. 컴퓨팅 사고 Computational Thinking, Scratch (0) | 2022.06.19 |