Notice
Recent Posts
Recent Comments
Link
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
관리 메뉴

Fear is a habit. I'm not afraid.

This is CS50 (3) Arrays 본문

basic

This is CS50 (3) Arrays

sylviaisthebest 2024. 6. 3. 00:05

Keyword

Compiling

Debugging

help50 and printf

debug50

check50 and style50

Data Types

Memory Arrays

Strings

Command-line arguments

Readability

Encryption

 

1. 컴파일링 compiling

#include <cs50.h>
#include <stdio.h>

int main(void)
{
    string name = get_string("What's your name?\n");
    printf("hello, %s\n", name);
}

1) int main(void)

- main이라는 함수

- 프로그램의 시작점으로써 실행 버튼을 클릭하는 것과 같음

2) #include <stdio.h>

- 라이브러리, 누군가 이미 작성해둔 코드

- 정확하게 말하면 stdio.h는 헤더 파일로 C언어로 작성되어 있으며 파일명이 .h로 끝나는 파일

- 이 파일에는 printf 함수의 프로토타입이 있어서 Clang 컴파일러가 프로그램을 컴파일할 때 printf가 무엇인지 알려주는 역할

3) ; (세미콜론)

- 새 줄을 의미

- 커서가 다음 줄로 넘어가도록 한다.

4) #include <cs50.h>

- cs50 라이브러리를 사용한 프로그램을 컴파일할 때는 clang에 또 하나의 프로그램이 필요함: lcs50

clang -o hello hello.c -lcs50
./hello

5) clang

- 소스코드를 머신코드로 변경해주는 실행어

6) -o hello hello.c

- hello.c 파일을 컴파일하고, hello라는 이름의 실행파일을 생성

7) -lcs50

- clang에게 cs50 라이브러리에 있는 모든 0과 1을 연결하라는 것을 의미

make hello
./hello

8) make hello

위의 긴 명령어와 의미가 동일함.

 

[ 컴파일링 compiling 단계 ]

1. preprocessing

2. compiling

3. assembling

4. linking

 

1. 전처리 preprocessing

- 소스코드가 있을 때, 맨 위의 두 줄은 두 라이브러리 파일을 추가하라고 말하고 있음.

- # hash 기호로 시작하는 이 두 줄은 해당 파일의 실제 코드로 대체된다.

- 즉, cs50을 추가하는 첫 줄 대신 clang이 cs50.h 파일에 직접 들어가 해당되는 코드를 가져와서 hello.c라는 파일에 붙여넣는 것

ex)

...
string get_string(string prompt);
int printf(string format, ...);
...
int main(void)
{
    string name = get_string("What's your name?\n");
    printf("hello, %s\n", name);
}

 

2. 컴파일링 compiling

- 소스 코드를 어셈블리어로 바꾸는 단계, 더 구체적인 단계를 뜻함.

- 프로그램이 컴파일될 때 어셈블리 코드로 바뀜

...
main:                         # @main
    .cfi_startproc
# BB#0:
    pushq    %rbp
.Ltmp0:
    .cfi_def_cfa_offset 16
.Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
.Ltmp2:
    .cfi_def_cfa_register %rbp
    subq    $16, %rsp
    xorl    %eax, %eax
    movl    %eax, %edi
    movabsq    $.L.str, %rsi
    movb    $0, %al
    callq    get_string
    movabsq    $.L.str.1, %rdi
    movq    %rax, -8(%rbp)
    movq    -8(%rbp), %rsi
    movb    $0, %al
    callq    printf
    ...

- clang에 의해 컴파일되면 C로 작성된 소스 코드는 어셈블리 코드라는 중간 단계로 바뀌고, CPU가 실제로 이해할 수 있는 언어에 가까워짐.

 

3. 어셈블링 assembling

- 실제 0과 1로 이루어진 머신 코드로 변경하는 단계

 

4. 연결 linking

- 3개의 다른 파일들을 clang이 컴파일해야 함

- 0과 1들을 하나의 큰 파일로 합치는 단계

 

Q. 만약 컴파일링 과정을 거치지 않기 위해 바로 머신코드로 우리가 원하는 프로그램을 작성하려고 한다면 어떤 문제가 있을까요?

더보기

컴파일러 vs 인터프리터

인터프리터란?

- 프로그램 생성 -> 파일 연결이나 머신 코드 생성이 필요하지 않음 -> 소스를 하나씩 실행함

 

컴파일러의 장점

- 컴파일러는 코드가 미리 머신 코드로 변환되기 때문에 최적화되고 효율적인 코드 실행이 된다. 즉, 프로그램 실행 속도가 빨라지고 런타임 오버헤드가 줄어든다. 또한 컴파일된 프로그램은 소스 코드를 공개하지 않고 배포할 수 있다.

- 더 작은 메모리를 사용한다.

- CPU 사용률이 낮다.

컴파일러의 단점

- 컴파일 단계에서 사전 지연이 발생하고 변경 사항은 적용하려면 다시 컴파일해야 한다.

- 실행 중에 사람이 읽을 수 있는 소스 코드가 없기 때문에 컴파일된 코드를 디버깅하기가 어렵다.

 

인터프리터의 장점

- 디버깅하기 쉽다

- 메모리를 자동으로 관리할 수 있어 메모리릭에 대한 위험이 줄어든다.

- 인터프리터로 작성된 언어는 컴파일러보다 더 유연하다.

인터프리터의 단점

- 해당 인터프리터된 프로그램만 실행할 수 있다.

- 인터프리터로 작성된 코드는 컴파일러로 작성된 코드보다 느리게 실행된다.

속도?

- 프로세스를 고려할 때 인터프리터는 컴파일러보다 빠르다. 그러나 프로그램이 이미 컴파일된 경우에는 컴파일된 프로그램의 실행이 인터프리터로 작성된 프로그램보다 빠르다.

4-5. 배열 Array

동적 배열의 단점

1. 동적 배열은 크기를 조정하는 속도가 느릴 수 있다.

- 동적 배열의 요소 수가 증가하여 배열의 크기를 조정해야 하는 경우 전체 배열을 메모리의 새 위치로 이동해야 하므로 시간과 리소스 측면에서 비용이 많이 듦.

2. 동적 배열은 필요할 때 더 많은 요소를 저장할 수 있도록 추가 공간을 할당해야 하므로 꼭 필요한 것보다 더 많은 메모리를 할당해야 하는 경우가 많다. 이로 인해 메모리가 낭비될 수 있다.

3. 동적 배열은 서로 다른 유형의 요소를 저장할 수 없다.

- 모든 배열과 마찬가지로 동적 배열은 같은 데이터 타입에 대한 데이터만 저장할 수 있으므로 동일한 배열에 서로 다른 유형의 요소를 저장할 수 없다. 서로 다른 유형의 요소를 저장해야 하는 경우에는 다른 데이터 구조를 사용해야 한다.

 

6. 문자열과 배열 Strings

- 실제로는 바이트가 아니라 배열로 저장이 된다.

- string은 정해진 크기를 가질 수 없다.

- 길이가 3인 문자열은 사실 4바이트를 차지한다. (\0의 null문자를 포함하여)

string names[4];

names[0] = "EMMA";
names[1] = "RODRIGO";
names[2] = "BRIAN";
names[3] = "DAVID";

printf("%s\n", names[0]);
printf("%c%c%c%c\n", names[0][0], names[0][1], names[0][2], names[0][3]);

- 첫 번째는 이름들의 배열의 인덱스를 나타내고, 배열의 인덱스는 어떤 배열의 특정 위치를 의미한다.

 

Q. 널 종단 문자는 왜 필요할까요?

더보기

널 문자가 없으면 strcat, strncpy와 같은 함수는 문자열이 끝나는 위치를 알지 못하고 인접 메모리를 계속 읽어서 정의되지 않은 동작이 발생할 수 있다.

널 문자는 문자열의 경계를 표시하여 버퍼 오버플로 및 메모리 액세스 위반을 방지하는 데 도움이 된다.

 

7. 문자열의 활용 Strings

- 소문자를 대문자로 바꾸는 코드 (ASCII 규칙을 이용함)

#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')
        {
            printf("%c", s[i] - 32);
        }
        else
        {
            printf("%c", s[i]);
        }
    }
    printf("\n");
}

 

8. 명령행 인자 Command-line arguments

#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");
    }
}

main 함수는 0을 반환한다. (0 = 문제없음)

argv[0]에는 처음 입력하는 프로그램 이름이 들어간다.

 

Q. 명령행 인자는 프로그램의 확장성에 어떤 도움이 될까요? 구체적인 예시를 떠올려보세요.

더보기
  1. 유연성 향상: 명령행 인자를 사용하면 사용자가 프로그램 실행 시 다양한 옵션을 설정할 수 있습니다. 예를 들어, 파일 경로, 구성 설정, 출력 형식 등을 명령행 인자로 받아 프로그램을 더 유연하게 실행할 수 있습니다.
  2. 재사용성 증가: 다양한 설정과 옵션을 명령행 인자로 받아들이면, 동일한 프로그램을 여러 가지 상황에 맞게 재사용할 수 있습니다. 이렇게 하면 중복 코드를 줄이고 프로그램의 재사용성을 높일 수 있습니다.
  3. 자동화  스크립트 통합: 명령행 인자는 프로그램을 스크립트와 쉽게 통합할 수 있게 해줍니다. 이를 통해 복잡한 작업을 자동화할 수 있으며, 배치 작업이나 크론 작업 등에서 유용하게 사용할 수 있습니다.
  4. 테스트 용이성: 명령행 인자는 다양한 입력 시나리오를 테스트하기 쉽게 만듭니다. 여러 가지 입력 값을 빠르게 변경하면서 프로그램의 동작을 확인할 수 있기 때문에, 테스트를 효율적으로 수행할 수 있습니다.
  5. 사용자 편의성 향상: 프로그램 사용자에게 더 많은 제어 권한을 제공합니다. 사용자는 명령행 인자를 통해 프로그램의 동작 방식을 쉽게 조정할 수 있습니다. 이는 사용자 경험을 향상시키는 중요한 요소입니다.
  6. 확장성: 새로운 기능이나 옵션을 추가할 때 명령행 인자를 통해 쉽게 확장할 수 있습니다. 코드 구조를 크게 변경하지 않고도 새로운 인자를 추가하는 방식으로 프로그램을 확장할 수 있습니다.