부스트코스 - 모두를 위한 컴퓨터 과학 (CS50 2019) - 배열
업데이트:
컴파일링
#include <stdio.h> // 누군가 이미 작성해둔 코드를 모아놓은 라이브러리
int main(void)
{
printf("hello, world!\n");
}
컴파일링의 4단계
- 전처리 - 라이브러리로 불러왔던 #으로 시작하는 구문을 실제 사용중인 함수를 정의한 코드로 불러오는 처리를 합니다.
- 컴파일 - 컴파일이 진행되면 작성되었던 코드들은 어셈블리어로 치환된다.
- 어셈블링 - 컴파일되어 어셈블리어로 변경된 코드를 다시 0 과 1로 이루어진 머신코드로 변경한다.
- 링킹 - 이렇게 머신코드로 변경된 코드들은 # 으로 불러온 라이브러리와 직접 작성한 코드가 있는데 이 각각 작성되고 변환된 코드들을 하나로 연결해주는 작업을 하게 된다.
즉 컴파일이란 사용자가 작성한 코드들을 컴퓨터가 이해 할 수 있는 머신코드로 변경하고 합쳐지는 과정이고 이를 통해 사용자는 사용자 친화적인 코드로 프로그래밍을 할 수 있도 컴퓨터는 컴퓨터가 이해 할 수 있는 언어로 받아들일 수 있게 된다.
디버깅
코드를 작성하다보면 수많은 버그에 직면하게 된다. 버그란 의도하지않은 동작으로 인해 발생하는 에러 문제점 트러블이다. 그리고 이 문제를 해결하는 과정은 바로 디버깅 이라고 한다.
개발자는 끊임없이 버그와 싸우고 어쩌면 코드를 작성하는것보다 버그와 씨름하는 시간이 더 많을수도있다. 내가 만들어낸 버그도 있겠지만 동료가 만든 버그가 있을수도있고 또 내가 다른 사람의 코드를 수정하다 버그가 발생할 수도있다. 이런떄 유용한 도구가 디버깅 도구들인데 cs50 에서는 sandbox 에서 help50 란 명령어로 명확한 디버깅 메세지를 출력해주는 도구를 제공한다.
나는 vscode 컴파일 과정에서 발생하는 에러메세지를 보고 문제들을 해결하고 있다.
다른 사람들과 스터디하는 과정에서 컴파일에 따라 에러를 출력하는 상황이 다른것을 알게 되었는데 아마 얼마나 컴파일과정에서 엄격하게 디버깅을 수행하느냐에따라 다른것같다.
에러메세지는 대부분 영어로 나오기때문에 잘 모르겠다면 그냥 복붙으로 구글에 바로 검색한다. 대부분의경우 스택오버플로우에 내가 겪은 문제를 똑같인 겪고 글을 올리고 해결방법이 제시된 경우가 90%확률로 존재한다.
코드를 작성하면서 printf()
로 결과를 확인해가면서 코드를 작성하는것도 디버깅이다. 디버깅이란 따로 어떤 특정 행위를 지칭하긴하지만 코드를작성하면서 확인과정을 거치는 모든것을 디버깅이라고 할 수 있겠다.
이 강의에서는 기존 sandbox 보다 좀더 효율적인 디버깅 도구들을 갖고있는 IDE 를 소개해주는데 CS50 IDE 라고 한다. 깃헙아이디로 로그인하고 바로 사용 가능한것같고 꽤 편안한 도구들을 많이 포함하고 있다. 그러나 난 역시 vscode를 계속 사용하기 때문에 날것 그대로의 에러 메세지를 보면서 강의과제와 실습을 진행하고 있다.
코드의 디자인
코드에도 디자인이 필요하다. 코드컨벤션 이라고도 부르는데 일종의 코드를 작성하는 사람들끼리의 약속이다.
이는 동일한 언어를 사용한다고 했을때 어느프로젝트 코드를 보든 일관성있게 코드를 읽어 나갈 수 있게 해주고 협업하는 과정에서도 동료간에 업무의 효율성과 버그의 발생 확률을 낮춰주는 역할을 할 수 있다.
#include <stdio.h>
int main(void) { // 중괄호를 여기서부터 사용할지
printf("hello, world\n");
}
int main(void)
{ // 여기서부터 사용할지는 입사한 회사의 기준마다 다르다.
printf("hello, world\n");
}
int main(void)
{ // 이런식으로 작성한다면 심지어 이게 수백줄의 코드가 다 이렇다면 정말 괴롭다.
printf("hello, world\n");
}
코드스타일은 우리가 문서를 작성할때 띄어쓰기와 문단나누기 그리고 들여쓰기 같은것과 동일하다. 규칙성있게 작성한다면 한달후 또는 몇년후에 내가 다시 봐도 충분히 봐줄만한 코드일것이다.
c 에도 있을지 모르겠지만 javascript 같은경우 코드스타일을 일관성있게 작성하게 도와주는 도구가 존재한다. cs50에서도 styl50이라는 라이브러리로 기준 스타일에서 벗어나면 에러를 출력하는 도구를 제공한다.
고무오리 디버깅
누군가에게 내가 작성한 코드를 설명하는것은 매우 중요하다. 하지만 사람을 붙잡고 이런 설명을 하는것이 어려울수도 있다. 그래서 개발자들사이에서는 고무오리디버깅 이라는 디버깅 방법이 따로있다. 고무오리를 들고 내가 작성한 코드에 대해서 설명하는것이다. 피드백은 중요하지않다. 이 설명 과정에서 내가 빼먹은걸 발견하게 된다면 성공적인 디버깅이었고 성공적인 코드리뷰 였다고 할 수 있겠다.
배열(1)
C 의 다양한 자료형
bool // 1bytes
char // 1bytes
int // 4bytes
float // 4bytes
long // 8bytes
double // 8bytes
string // ? bytes
C 샘플코드 01
#include <stdio.h>
int main(void)
{
char c1 = 'H'; // 문장을 쓸때는 "" 을 쓰지만 한글자일떄는 '' 로 구분해서 작성한다.
char c2 = 'I';
char c3 = '!';
printf("%c %c %c\n", c1, c2, c3); // HI!
// 정수로 출력하기위해 형변환
printf("%i %i %i\n", (int) c1, (int) c2, (int) c3); // 72, 73, 33
}
C 샘플코드 02
#include <stdio.h>
int main(void)
{
int score1 = 72;
int score2 = 73;
int score3 = 33;
printf("Average: %i\n", (score1 + score2 + score3) / 3);
}
위 코드는 세과목 점수의 평균을 구하는 코드이다. 아무 문제없이 동작하는 코드이지만 몇가지 문제점이 존재한다.
- 부분점수 즉 소수점이하의 점수 표현이 안된다.
- 과목 점수가 하드코딩이라 다른 학생의 점수에 대해서는 동적으로 입출력이 불가능하다.
- 입력받는 값과 변수는 다르지만 중복되어 보이는 코드가 존재한다.
C 샘플코드 03
#include <stdio.h>
int main(void)
{
int scores[3];
scores[0] = 72;
scores[1] = 73;
scores[2] = 33;
printf("Average %i\n", (scores[0] + scores[1] + scores[2]) / 3);
}
샘플코드 02 에서 조금 개선했지만 여전히 중복되어 보이는 코드도 보이고 코드의 라인이 줄어든것처럼 보이지도 않는다.
배열(2)
위의 코드는 여전히 가독성이 떨어진다. 반복사용된 코드도 여전히 보인다. 하드코딩되어있어 동적으로 입출력을 하는것이 불가능하다.
C 샘플코드 04
#include <stdio.h>
const int N = 3; // 전역변수로 상수 지정, 어느곳에서 사용하던 같은 값을 갖게 된다.
int main(void)
{
int scores[N]; // 전역변수 상수 사용
scores[0] = 72;
scores[1] = 73;
scores[2] = 33;
printf("Average %i\n", (scores[0] + scores[1] + scores[2]) / N); // 전역변수 상수 사용
}
조금더 코드를 수정해서 동적으로 유연성있게 만들어보자
C 샘플코드 05
#include <stdio.h>
float average(int length, int array[]);
int main(void)
{
int n = get_int("Number of scores: ");
int scores[n];
for (int i = 0; i < n; i++)
{
scores[i] = get_int("Score %i: ", i);
}
printf("Average: %.1f\n", average(n, scores));
}
// 사용자 함수 생성
float average(int length, int array[])
{
int sum = 0;
for (int i = 0; i < length; i++)
{
sum += array[i];
}
return (float) sum / (float) length; // 정수를 정수로 나누면 정수를 출력하기때문에 형변환 필요
}
문자열과 배열
int 형을 가지는 하나의 숫자는 4바이트의 메모리 크기를 가진다. char 형을 가지는 한 문자는 1byte 의 메모리 영역을 갖는다.
string string = "HI!"; // string 은 cs50 라이브러리를 사용했을때만 정의 할 수 있는 형태
위와같이 정의했을때 하나의 글자이지만 사실은 H, I, ! 의 3byte 영역을 갖는 글자 배열이라고도 볼 수 있다. 문자열에서 특이한점은 종단문자인 \0 or null
의값을 항상 마지막에 갖는다는것이다. 이유는 이 문자의 배열의 끝이 어딘지 표시하기위한 c 언어의 특징이라고도 볼 수있다. 이는 8비트를 모두 0으로 처리되는 값이다.
즉 HI! 가 차지하고있는 메모리영역은 3byte 가 아니라 4byte라고 볼 수 있다.
문자열의 활용
사용자로부터 입력받은 문자를 대문자로 변환하는 프로그램 만들어보기
#include <stdio.h>
#include <string.h> // strlen() 문자열의 길이를 반환해주는 함수를 갖고있는 라이브러리
char *get_string(char *text) { // cd50 의 get_string 함수를 흉내낸것
static char str[100];
printf("%s", text);
scanf("%s", str);
return (char*)str;
}
int main(void) {
char s[2]; // string 으로 문자열을 받는 형식이 따로 없기때문에 char 로 배열값을 임의로지정
s[0] = *get_string("Before: ");
s[1] = *get_string("Before: ");
printf("After: ");
for (int i = 0, n = strlen(s); i < n; i++) {
if (s[i] >= 'a' && s[i] <= 'z') {
// ASCII 코드 번호를보면 알파벳 대문자와 소문자의 차이는 32만큼의 동일한 차이가 나는데 그걸 이용하여 소문자를 대문자로 변환
printf("%c ", s[i] - 32);
} else {
printf("%c ", s[i]);
}
}
}
명령행 인자
#include <stdio.h>
// cs50 에서는 string 타입이 있기때문에 char *argv[] => string artgv[] 로 나온다.
int main(void) => int main(int argc, char *argv[])
c 프로그래밍의 제일 시작점인 main 함수에 지금껏 void 만 사용해 왔는데 int argc, char *argv[]
이런 형태의 인자값을 넘겨주면서 시작 할 수도있다.
- argc 메인함수에 전달되는 정보의 갯수를 의미
- argv 메인함수에 전달되는 실질적인 데이터로 문자열의 배열을 의미한다.
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc == 2) {
printf("hello, %s\n", argv[1]);
} else {
printf("hello, world\n");
}
}
위와 같이 코드를 작성하고 파일을 실행하면서 한칸띄우고 뒤에 문자열을 입력하면 해당 문자열을 바로 main 함수를 실행하면서 출력하게된다.
이러한것들을 명형행 인자라고 한다.
댓글남기기