#include <stdio.h>
int main(void)
{
printf("hello world\n");
}
stdio.h 는 헤더 파일로, 남이 이미 작성한 코드인 라이브러리임. clang 컴파일을 할 때 prinf가 무엇인지 알려주는 역할
clang
https://www.boostcourse.org/cs112/joinLectures/41487?isDesc=false
모두를 위한 컴퓨터 과학 (CS50 2019)
부스트코스 무료 강의
www.boostcourse.org
1. 컴파일링
컴파일 / compiling 은 총 네개의 단계로 구성됌
eg)
hello.c
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string name = get_string("what's your name\n");
printf("hello, %s\n" );
}
- preprocessing (전처리 단계)
#include <cs50.h>
#include <stdio.h>
clang 혹은 make로 프로그램이 실행되면 #으로 시작되는 코드는 해당 파일(<cs50.h>) 안의 실제 코드로 대체됌.
#include <cs50.h> -> string get_string(string prompt); (와 기타등등)
- compiling
프로그램이 컴파일 될 때, 전처리 단계를 거치고
소스 코드에서 어셈블리 코드로 (중간단계) 변환 됌
- assembling
어셈블리 코드에서 0과 1로 이루어진 머신 코드로 변환하는 단계
- linking
마지막 단계에서 머신 코드들을 하나의 파일로 합침
예시의 코드 안에 총 3개의 파일, cs50.c, stdio.c, hello.c 가 있는데 모두 어셈블리 코드로 변환된 후
마지막 단계인 linking에서 하나의 큰 파일로 합쳐짐.
-> hello 아님 a.out
과거엔 01에서 어셈블리 언어 그리고 지금의 c, c++ , python 등의 프로그래밍 언어로 발전해옴.
소스 코드 (input) 에서 머신코드 (output) 사이에서 위와 같은 과정이 일어나고 있음.
2. 디버깅
컴퓨터 프로그래밍 수업에서 배울 수 있는 아주 중요한 기술은 코드 작성 뿐 아니라 코드를 디버깅하는 것이다.
문법적 버그
논리적 오류
논리 오류가 생길 경우 printf 함수를 통해 실제로 어떤게 발생하고 있는지 시각화해서 오류를 찾아내자.
그냥 화면만 뚫어지게 쳐다보는게 아니라.
sandbox.cs50.io 에서 더 발전된 버젼인 cs50 ide 도입
CS50 IDE
integrated development environment for students and teachers
ide.cs50.io
디버거를 사용은 c 뿐만 아니라 다른언어에서도 평생 사용할 수 있는 기술
디버거를 사용해, 코드를 한 단계 한 단계식 실행해 실제로 프로그램이 어떻게 진행되고 있는지를 쪼개서 확인해가며 논리 오류가 생긴 지점을 시각적으로 발견할 수 있음.
3. 코드의 디자인
코드 작성만큼 중요한 것이 바로 코드가 정확한지를 확인하는 테스트를 만드는 것
코드 작성 시에는 나 자신과 다른 사람들이 읽기 쉽도록 그리고 무엇보다 유지보수하기 쉽게 작성해야함.
고무 오리
때로는 코드에 포함된 오류를 해결할 때 앞서 소개한 help50, debug50, check50과 같은 프로그램들이 존재하지 않거나, 있다 하더라도 디버깅에 큰 도움이 안 될 수 도 있습니다.
이 때는 먼저 한숨 돌리고 직접 곰곰히 생각해보는 수 밖에 없습니다.
한가지 유명한 방법으로 ‘고무 오리’와 같이 무언가 대상이 되는 물체를 앞에 두고, 내가 작성한 코드를 한 줄 한 줄 말로 설명해주는 과정을 거쳐볼 수 있습니다.
이를 통해 미처 놓치고 있었던 논리적 오류를 찾아낼 수도 있습니다.
4. 배열 (1)
메모리
C에는 아래와 같은 여러 자료형이 있고, 각각의 자료형은 서로 다른 크기의 메모리를 차지합니다.
- bool: 불리언, 1바이트
- char: 문자, 1바이트
- int: 정수, 4바이트
- float: 실수, 4바이트
- long: (더 큰) 정수, 8바이트
- double: (더 큰) 실수, 8바이트
- string: 문자열, ?바이트
RAM
은 소프트웨어 구동 시에 정보가 저장되는 곳
배열 (array)
C에서 여러개의 값을 가진 하나의 변수를 만들고 싶을 때, 배열이라고 하는 걸 사용함.
배열은 값들의 리스트로 모두 같은 자료형의 값들이 같은 변수에 저장됌.
전역 변수(global variable)
함수 바깥에서 선언하는 변수
5. 문자열과 배열
문자열 (string)은 문자들(char)의 배열임.
eg) string name = "string"
s[0] == s
s[1] == t
...
s[4] == g
엄밀히 따지면 틀린 말.
string 은 글자 수 만큼 바이트가 증가
그러면 문자가 언제 끝나는지 컴퓨터에 알려줘야함.
이를 널 문자라고 부름 \0
그래서 길이가 3인 문자열은 사실 4바이트를 차지.
코드 예시
#include <cs50.h>
#include <stdio.h>
int main(void){
string names[4];
names[0] = "EMMA";
names[1] = "RODRIGO";
names[2] = "BRIAN";
names[3] = "DAVID";
엠마이름을출력하고 싶을때
1.
printf("%s\n", names[0]);
2.
printf("%c%c%c%c\n", names[0][0], names[0][1], names[0][2], names[0][3]);
}
~/ $ make names
clang -ggdb3 -O0 -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow names.c -lcrypt -lcs50 -lm -o names
~/ $ ./names
EMMA
EMMA
~/ $
2번이 바로 string 문자열을 출력할 때 거치는 단계를 직접 지정한 형태
메모리의 관점에서 프로그램이 실행 됄 때
E M M A \0 이렇게 총 5바이트를 사용해 메모리에 저장함. (RAM)
메모리의 구체적인 위치를 지정하기만 하면 그 메모리 칸에 어떤 데이터가 들었는지 얼마든지 알 수 있음.
이를 통해 printf 나 get_string 같은 함수가 마법같이 발생하는게 아니라, 이런식으로 물리적인 영역을 분명히 거치며 조작을 통해 실행됨을 이해하기만 하면됌.
7. 문자열의 활용
메모리를 더 사용할 것이냐 vs 프로그램 시간을 단축시킬 것이냐.
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = get_string("Input: ");
printf("Output:\n");
1. for (int i = 0, i < strlen(s); i++) strlen은 string.h 에서 문자열의 길이로 정의된 코드
이경우 반복적으로 프로그램은 strlen(s)가 몇인지 확인하고 확인하고 그럼. 시간이 더 오래걸림
2. let n = strlen(s)
for (int i = 0, n = strlen(s) ; i++)
이경우 변수가 추가되어 메모리가 더 할당 되지만, for 문 이전에 이미 값이 정의되서 시간이 덜걸림 (큰차이가 아니더라도)
3. for (int i = 0, n = strlen(s) ; i < n; i++)
마지막으론 2번을 그냥 보기 좋기 적은 버젼
{
printf("%c\n", s[i]);
}
}
terminal
~/ $ make names
clang -ggdb3 -O0 -std=c11 -Wall -Werror -Wextra -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wshadow names.c -lcrypt -lcs50 -lm -o names
~/ $ ./names
Input: emma
Output:
e
m
m
a
~/ $
매우 기계어에 가까운 방식으로 소문자를 대문자로 바꾸기
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = get_string("Before: ");
printf("After: ");
for (int i = 0, n = strlen(s); i < n; i++)
{
if (s[i] >= 'a' && s[i] <= 'z') 만약단어가a부터z사이에존재한다면(소문자라면)
{
printf("%c", s[i] - 32); ascii 코드에서 대응하는 십진법 숫자값에 32를 뺸 값의 char 문자를 출력해라
}
else
{
printf("%c", s[i]);
}
}
printf("\n");
}
하지만 이미 존재하는 라이브러리를 사용하면 고작 한줄의 코드로 바꿀 수 있음.
남들이 써둔 코드를 잘 써먹자~
9. 명령행 인자
명령행 인자는 보통 실행하고자 하는 프로그램 뒤에 적는다
ex) clang hello.c -o hello
우리가 여태껏 많이 사용해온 main 함수를 보다 자세히 들여다볼 때가 왔습니다.
main도 그 형태를 보면 하나의 함수임을 알 수 있는데요, 이젠 더이상 main() 안에 기계적으로 void 라고 입력하는 대신 아래 코드와 같이 argc, argv 를 정의해보겠습니다.
#include <cs50.h>
#include <stdio.h>
int main(int argc, string argv[])
{
if (argc == 2)
{
printf("hello, %s\n", argv[1]);
}
else
{
printf("hello, world\n");
}
}
여기서 첫번째 변수 argc는 main 함수가 받게 될 입력의 개수입니다.
그리고 argv[]는 그 입력이 포함되어 있는 배열입니다. 프로그램을 명령행에서 실행하므로, 입력은 문자열로 주어집니다.
따라서 argv[]는 string 배열이 됩니다.
argv[0]는 기본적으로 프로그램의 이름으로 저장됩니다.
만약 하나의 입력이 더 주어진다면 argv[1]에 저장될 것입니다.
예를 들어 위 프로그램을 “arg.c”라는 이름으로 저장하고 컴파일 한 후 “./argc”로 실행해보면 “hello, world”라는 값이 출력됩니다.
명령행 인자에 주어진 값이 프로그램 이름 하나밖에 없기 때문입니다.
하지만 “./argc David”로 실행해보면 “hello, David”라는 값이 출력됩니다.
명령행 인자에 David라는 값이 추가로 입력되었고, 따라서 argc 는 2, argv[1] 은 “David”가 되기 때문입니다.
'인강 > 모두를 위한 컴퓨터 과학 cs50 2019' 카테고리의 다른 글
CS50 C언어 (0) | 2021.06.09 |
---|
댓글