컴퓨팅 사고력 기르기 (심화 편) (3)

productuidev·2022년 8월 24일
0

IT/CS 지식 쌓기

목록 보기
21/24
post-thumbnail

모두를 위한 컴퓨터 과학 (CS50)

하버드대학의 최고 인기강좌, 데이비드 말란 교수님의 CS50입니다. 프로그래밍을 처음 공부하는 비전공자 분들도 쉽고 재미있게 학습하실 수 있습니다.

boostcourse 바로가기
https://m.boostcourse.org/cs112/

C언어

강좌 IDE CS50 Sandbox (https://sandbox.cs50.io)

C 기초

stdio.h clang 컴파일러

  • C는 아주 오래되고 전통적인 순수 텍스트 기반의 언어
  • 스크래치 블록과 결과적으로 정확히 같음

  • int main(void) 는 스크래치의 “초록색 깃발을 클릭했을 때” 블록과 같은 역할, 시작한다는 의미
  • printf(“hello, world\n”) 은 스크래치의 “‘hello, world’라고 말하기” 블록과 같은 역할
  • \n은 줄바꿈의 기능 (키보드에서 ENTER의 기능과 동일)
  • #include <stdio.h>는 “stdio.h”라는 이름의 파일을 찾아서 “printf” 함수에 접근할 수 있도록 해줌
  • C로 작성한 코드는 “파일이름.c”로 저장해야 함 (확장자 “.c”는 C로 작성된 코드라는 의미) 직접 확장자로 .c를 붙여줘야 함.

컴파일러

  • binary number로 작성된 “머신 코드”로 변환해야 컴퓨터가 이해할 수 있음. 이런 작업을 컴파일러라는 프로그램이 수행. (0과 1)

  • 터미널창의 명령어 프롬프트에서 “$” 기호 옆에우리가 원하는 명령어를 입력
  • clang hello.c 라는 명령어는 “clang” 이라는 컴파일러로 “hello.c”라는 코드를 컴파일하라는 의미

문자열

형식지정자 string make

  • 스크래치의 ask함수와 가장 비슷한 것은 get_string 함수
  • 유의해할 점은 C는 오래된 언어이기 때문에 변수가 저장하는 데이터의 종류를 아주 정확하게 명시해줘야 함.
  • 저장하고자 하는 값의 종류가 문자열(string)이라는 것을 알려줘야 함 (형식지정자)
  • = 같다, 오른쪽에서 왼쪽으로 가는 화살표와 비슷한 역할 (할당 연산자)
  • get_string 함수가 사용자의 이름을 반환하면 그 이름을 anwser이라는 변수에 저장
  • 변수에 저장한다는 것은 컴퓨터의 메모리 어딘가에 사용자의 이름이 저장되어 있는 것
너무나 당연하게 이름은 숫자가 아닌 문자이기 때문에 컴퓨터에게 "answer에 들어갈 것들은 문자야!"라고 말해주는 것

조건문과 루프

int if while for

  • counter라는 변수에 숫자 저장
  • int는 변수가 정수(integer)
  • 초기값 0 할당

  • 변수의 값을 1씩 증가
  • counter에 1을 더한 값을 다시 counter에 저장(할당)한다는 뜻
  • 다양한 방법으로 코드를 간결하게 작성할 수 있음 (+1 = +=1 = ++ 모두 같은 결과)

  • 조건문 블록 (if / else / else if)

  • == : 오래전 사람들이 합의하길 =을 2개 사용하여 같다를 표현하자라고 정함 (일치 연산자)
  • 코드를 간결하게 작성하는 이유
    얼마나 효율적으로 코딩하는지 or 얼마나 적은 메모리나 CPU를 사용해서 수행하는지 (연산, 성능)

루프

  • 무언가를 계속 반복하는 것
  • true라는 항상 참이 되는 조건을 통해 while 루프가 영원히 수행

  • i<50이라는 조건을 추가한다면?

i는 0으로 설정
-> i는 50보다 작은가?
-> 작다
-> hello world를 출력한다
-> i를 1증가시킨다
-> i가 50보다 작은가?
-> (반복)
-> i가 50보다 작은가?
-> 작지 않다
-> 종료
  • 프로그래머들은 무언가를 셀 때 간단하게 정수를 나타내는 i를 사용
  • for를 사용하면 for ( ) 안에 각각 (변수 초기화; 변수 조건; 변수 증가) 에 해당하는 코드를 넣어서 간단하게 표현할 수 있음

가장 먼저 정수 값을 가지는 i라는 변수를 0으로 초기화
-> i가 50인지 매번 검사
-> 이를 만족하면 { } 안의 내용을 수행
-> i를 1씩 증가시킨다는 의미

자료형, 형식 지정자, 연산자

char long float double % && ||

데이터 타입

  • bool: 불리언 표현, (예) True, False, 1, 0, yes, no
  • char: 문자 하나 (예) 'a', 'Z', '?'
  • string: 문자열
  • int: 특정 크기 또는 특정 비트까지의 정수 (예) 5, 28, -3, 0
  • long: 더 큰 크기의 정수
  • float: 부동소수점을 갖는 실수 (예) 3.14, 0.0, -28.56
  • double: 부동소수점을 포함한 더 큰 실수
  • int는 대략 40억까지 셀 수 있기 때문에 40억 개 이상의 데이터를 가진 일부 거대 기업과 같은 상황이 아닌 일반 사용자들은 대부분 정수에 int를 사용

형식 지정자

여러가지 데이터 타입에 사용되는 형식 지정자

  • %c : char
  • %f : float, double
  • %i : int
  • %li : long
  • %s : string

기타 연산자 및 주석

  • +: 더하기
  • -: 빼기
  • *: 곱하기
  • /: 나누기
  • %: 나머지
  • &&: 그리고
  • ||: 또는
  • //: 주석

사람의 나이를 일 수로 환산하면 며칠인지 계산해볼까요?

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

int main(void)
{
    int age = get_int("what's your age?\n");
    printf("Your are at least %i days old.\n", age * 365);
}
# include <cs50.h>
# include <stdio.h>

int main(void)
{
    printf("Your are at least %i days old.\n", get_int("what's your age?\n") * 365);
}

주석

  • 주석은 왜 다는 것일까? : 주석은 이 코드가 무슨 일을 하는지 설명하는 것

만약 코드가 수 백, 수 천줄이 되면 주석이 없다면 부분 부분마다 어떤 일을 하는지 찾기 힘들 것. 이것은 꼭 타인이 아닌 자기 자신에게도 해당. 자신이 짠 코드라고 해도 한달 뒤, 일년 뒤에 보면 새롭기 때문. 그렇기 때문에 주석으로 잘 설명하는 습관이 중요.

C 표준 라이브러리

C 표준 라이브러리(C standard library)는 C 언어를 위한 표준 라이브러리로서, ANSI C 표준에 의해 명시되었다. 이것은 상위 집합인 C POSIX 라이브러리와 동시에 개발되었다. ANSI C가 국제 표준화 기구에 의해서 채택됨에 따라, C 표준 라이브러리는 또한 ISO C library로도 불린다. C 표준 라이브러리는 매크로, 타입 정의 그리고 문자열 처리나 수학적 연산, 입출력 프로세스, 메모리 할당과 다른 운영 체제 서비스 같은 작업을 위한 함수들을 제공한다.

유닉스 계열 시스템들은 일반적으로 공유 라이브러리 형태로 C 라이브러리를 가지지만, 헤더 파일들이 설치 시에 존재하지 않아서 C 개발이 불가능할 수도 있다. C 라이브러리는 유닉스 계열 운영 체제에서 한 부분으로 여겨진다. ISO C 표준을 포함한 C 함수들은 프로그램들에서 널리 사용되지만, 운영 체제 인터페이스의 한 부분이다. 유닉스 계열 시스템들은 일반적으로 C 라이브러리가 제거되면 동작할 수 없다.

다른 언어들과 달리, 원본 C 언어는 입출력 동작 같은 빌트인 함수들을 제공하지 않았다. 시간이 지나면서 C의 사용자 커뮤니티들은 생각을 공유하고 현재 C 표준 라이브러리라고 불리는 것을 구현하였다. 이러한 아이디어들 중 많은 수가 결국 표준 C 언어의 정의에 포함되었다.

유닉스와 C는 모두 벨 연구소에서 1960년대에에서 1970년대에 만들어졌다. 1970년대 동안 C 언어는 점점 유명해 졌다. 많은 대학교들과 단체들이 자신의 프로젝트를 위해 이 언어를 자신만의 형태로 만들었다. 1980년대의 시작에 이것들 간에 호환 문제가 발생하였다. 1983년 미국 국립 표준 협회(ANSI)는 위원회를 구성해서 C의 표준 명세를 확립하였고 이것은 ANSI C로 불린다. 이 작업은 1989년 C89라고 불리는 것이 만들어짐으로써 끝이나게 된다.

출처 - C 표준 라이브러리 위키백과

사용자 정의 함수, 중첩 루프

왜 프로그래머들은 복사 붙여넣기 (Copy & Paste)를 하는가?

프로그래밍을 하다 보면 가끔 반복적으로 작성해야 하는 코드가 있다. 이런 코드를 여러번 쓰지 않고 함수 형태로 저장해두면 코드를 간결하고 이해하기 쉽게 만들 수 있다. 또한 하나의 루프로 수행하기 힘든 작업을 중첩 루프를 이용하여 해결한다.

사용자 정의 함수 중첩 루프

사용자 정의 함수

  • cough 3번 반복
#include <stdio.h>

int main(void)
{
    printf("cough\n");
    printf("cough\n");
    printf("cough\n");
}
  • 반복 단순화
#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        printf("cough\n")
    }
}
  • 동일한 작업을 반복하는 것을 사용자 정의 함수를 이용해 코드 단순화
  • void를 입력하고 원하는 함수명(cough)을 적은 뒤 괄호 안에 void를 적어줌
#include <stdio.h>

void cough(void)
{
    printf("cough\n")
}

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        cough();
    }
}
  • main 함수를 위로 올리고 cough 함수를 내려본다면 오류가 생김.
    C는 오래되었고 똑똑하지 않기 때문에 아래에 cough라는 함수가 있을 것이라 생각하지 못함
  • cough함수를 전부 본 적은 없어도 이름은 본적이 있으니 main 함수에 나올 때까지 코드를 계속 읽도록 하는 것
#include <stdio.h>

void cough(void);

int main(void)
{
    for (int i = 0; i < 3; i++)
    {
        cough();
    }
}

void cough(void)
{
    printf("cough\n");
}
  • 원하는 횟수만큼 cough를 출력
    main 함수 안으로 돌아가보면 cough(3) 이라는 한 줄의 코드를 통해서 3이라는 값을 cough 함수에 전달하여 cough를 세 번 출력
#include <stdio.h>

void cough(int n);

int main(void)
{
    cough(3);
}

void cough(int n)
{
    for (int i = 0; i < n; i++)
    {
        printf("cough\n");
    }
}

중첩 루프

  • 화면에 여러 개의 이미지를 가로나 세로로 여러 개 이어서 출력하고 싶다면
#include <cs50.h>
#include <stdio.h>

int main(void)
{
    int n;

    do
    {
        n = get_int("Size: ");
    }
    while (n < 1);

    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            printf("#");
        }
        printf("\n");
    }
}
  • do{…} while()을 이용해서 while( )의 조건이 만족할때 까지 get_int 함수로 사용자가 입력값을 받아 n에 저장.
  • do{ }while()을 사용하면 조건과 상관없이 최소한 한 번은 { }안의 내용을 실행할 수 있음.
  • for 루프를 두 번 중첩해서 돌면서 “#”을 출력
  • 첫 번째 루프에서는 변수 i를 기준으로 n번 반복하고, 그 안의 내부 루프에서는 변수 j를 기준으로 n번 반복.
  • 내부 루프에서는 “#”을 출력하고, 내부 루프가 끝날 때마다 줄바꿈을 수행.
  • 최종적으로는 가로가 n개, 세로가 n개인 “#”이 출력

하드웨어의 한계

컴퓨터는 우리가 작성한 프로그램을 구동하기 위해 다양한 물리적 장치를 사용한다. 그 중 하나는 메모리로, 프로그램이 필요한 정보가 저장되는 곳이다. 메모리의 용량은 무한하지 않기 때문에, 때때로 프로그램에서 우리가 의도하지 않은 오류가 발생하기도 한다.

메모리 오버플로우

  • 메모리 용량이 프로그램의 구동에 미치는 영향
  • 컴퓨터는 RAM(랜덤 액세스 메모리)이라는 물리적 저장장치를 포함하고 있음.
  • 작성한 프로그램은 구동 중에 RAM에 저장됨 RAM은 유한한 크기의 비트만 저장할 수 있기 때문에 때때로 부정확한 결과를 내기도 함

부동 소수점 부정확성

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

int main(void)
{
    // 사용자에게 x 값 받기
    float x = get_float("x: ");

    // 사용자에게 y 값 받기
    float y = get_float("y: ");

    // 나눗셈 후 출력
    printf("x / y = %.50f\n", x / y);
}
x: 1
y: 10
x / y = 0.10000000149011611938476562500000000000000000000000

나눈 결과를 소수점 50자리까지 출력하기로 하고, x에 1을, y에 10을 입력하면 정확한 결과는 0.1이 되어야 하지만, float 에서 저장 가능한 비트 수가 유한하기 때문에 다소 부정확한 결과를 내게 된다

정수 오버플로우

1부터 시작하여 2를 계속해서 곱하여 출력하면

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    for (int i = 1; ; i *= 2)
    {
        printf("%i\n", i);
        sleep(1);
    }
}
...
1073741824
overflow.c:6:25: runtime error: signed integer overflow: 1073741824 * 2 cannot be represented in type 'int'
-2147483648
0
0
...

변수 i를 int로 저장하기 때문에, 2를 계속 곱하다가 int 타입이 저장할 수 있는 수를 넘은 이후에는 아래와 같은 에러와 함께 0이 출력됨. 정수를 계속 키우는 프로그램에서 10억을 넘기자 앞으로 넘어갈 1의 자리가 없어진 것. int에서는 32개의 비트가 다였기 때문. 그 이상의 숫자는 저장할 수 없는 것.

사례 1

1999년에 큰 이슈가 되었던 Y2K 문제는 연도를 마지막 두 자리수로 저장했던 관습 때문에 새해가 오면 ‘99’에서 ‘00’으로 정수 오버플로우가 발생하고, 새해가 2000년이 아닌 1900년으로 인식된다는 문제였습니다. 수백만 달러를 투자해서 프로그래머들에게 더 많은 메모리를 활용해서 이를 해결하도록 하였습니다. 이는 통찰력 부족으로 발생한 아주 현실적이고 값비싼 문제였습니다.

사례 2

비행기 보잉 787에서 구동 후 248일이 지나면 모든 전력을 잃는 문제가 있었습니다. 왜냐하면 강제로 안전 모드로 진입하였기 때문입니다. 이는 소프트웨어의 변수가 248일이 지난 뒤에 오버플로우가되어 발생하였기 때문이었습니다. 248일을 1/100초로 계산하면 대략 2의 32제곱이 나옵니다. 보잉을 설계할때 사용한 변수보다 너무 커졌던 것입니다. 이를 해결하기 위해 주기적으로 재가동을 하여 변수를 다시 0으로 리셋했습니다.따라서 다루고자 하는 데이터 값의 범위를 유의하며 프로그램을 작성하는 것이 중요합니다.

생각해보기

Y2K와 보잉787과 같은 문제를 방지하기 위해서는 프로그램을 어떻게 설계해야 할까요?


보통 프로그래밍을 공부한 사람들이 시작하는 게 C 언어라고 하는데 나도 이 강의를 통해 처음 가볍게 C 언어를 접했다. 일상적으로 쓰던 조건문, 반복문, 함수 등에 대해 배경을 이해할 수 있었다.

더 읽어볼 자료

여러 종류의 프로그래밍 언어가 속속 등장하는 배경을 이해하기 위해서는 그것의 몇 가지 속성을 먼저 살펴봐야 한다.

  • 스위칭 코스트, 즉 전환 비용이다. 프로그래밍 언어는 학습하기가 쉽지 않은데다 일정 수준의 프로그래밍 역량을 갖추기까지 많은 시간이 소요된다. 한번 배운 언어를 다른 언어로 갈아타기란 결코 쉬운 작업이 아니다.
  • 프로그래밍 언어는 또한 네트워크 효과가 존재한다. 더 많은 개발자가 참여할수록 개발 환경뿐 아니라 언어의 가치도 높아진다.
  • 상품이라는 측면도 간과해서는 안 된다. 언어나 컴파일러가 상용 제품으로 판매됨으로써 기업 수익의 직접적 자양분이 되기도 한다. IBM이 개발한 최초의 고급 프로그래밍 언어 '포트란'이 대표적인 사례다. 1950년대 말 포트란이 IBM에 알토란 같은 수익을 안겨주자 너도나도 고급 언어 컴파일러 개발에 뛰어든 사례를 이를 방증해준다.

프로그래밍 언어의 출현 배경은 개발 주체(개인, 기업, 국가)에 따라 상이하다. 각각의 주체에 따라 성장 경로나 개발 의도가 차이를 보인다. 루비나 PHP와 같은 프로그래밍 언어는 순수한 개인의 호기심, 즉 해커문화의 산물이라 할 수 있다.

하지만 최근 들어서는 개인이나 연구자들에 의해 탄생하는 언어는 그리 도드라지지 않는다. '다트', '고', '핵', '스위프트'처럼 IT 거인들이 새로운 언어를 개발하고 소개하며 보급하는 흐름이 뚜렷하다. 이러한 변화는 언어의 보편성을 획득하기 위한 효율적 수단이 기업으로 집중되고 있다는 것을 의미한다.

창병모 숙명여대 교수(IT 학부)는 “학계나 개인들도 새로운 언어를 실험적으로 만들고 있는데 널리 보급되기가 어렵다”면서 “언어가 좋다고, 기능이 좋다고 해서 보급되고 되는 것은 아니고 어떤 플랫폼 위에서 쓰이느냐와 관련이 깊다”고 설명했다. 잘 만든 언어라도 특정 플랫폼과 결합되지 않으면 쓸 개발자를 만나지 못하는 게 개발자 생태계의 특징이다.

애플용 애플리케이션을 개발하려면 '오브젝티브C'나 스위프트, 윈도우폰 앱을 위해선 'C#'을, 안드로이드 앱 개발을 위해선 '자바'를 배워야 하는 것처럼, 특정 플랫폼을 장악한 IT 대기업들이 신규 언어 보급에 이점이 클 수밖에 없다. IT 거인들이 언어 개발을 주도할 수밖에 없는 중요한 배경 중 하나라고 할 수 있다.

특정 기업이 주도하는 프로그래밍 언어 개발은 일반 개발자나 학자와 달리 개발자 생태계 장악이 첫 번째 목적이다. 하지만 개발자 생태계는 자연스럽게 특정 소프트웨어나 하드웨어로의 선호로 이어지고, 이는 다시 해당 기업의 수익을 높이는 데 기여하게 된다. 프로그래밍 언어의 네트워크 외부성과 비즈니스 모델이 만나는 지점이기도 하다.

마이크로소프트는 이같은 수익 구조를 가장 잘 이해하고 있는 대표적인 소프트웨어 기업이다. 프로그램 언어의 주도권을 잡아 그 언어와 궁합이 맞는 소프트웨어와 하드웨어 판매로 연결시키고 있다.

창병모 교수는 “컴퓨팅 환경이 바뀌면 자연스럽게 프로그래밍 언어도 바뀌어왔다”라며 “언어 전쟁은 새로 등장하는 모바일 환경을 장악하기 위한 주도권 싸움으로 이해할 수 있다”고 분석했다. 언어를 장악하게 되면 전체 개발 생태계를 주도할 수 있는 인프라가 마련된다는 의미다.

언어 그 자체가 상품이 되는 경우도 여전히 존재한다. PHP처럼 프로그래밍 언어가 오픈소스화되는 경향이 짙어지고 컴파일러를 판매하는 비즈니스도 줄어드는 경향이지만, 상품으로서 가치에 집착하는 사례가 완전히 사라진 것은 아니다. 자바를 둘러싼 오라클과 구글의 소송전은 이러한 측면을 잘 일깨워준다. 자바를 썬마이크로시스템즈로부터 넘겨받은 오라클은 2010년 8월 구글에 자바 특허 침해 소송을 제기했다. 당시 오라클은 구글의 안드로이드가 자바를 특허를 침해했다며 10억달러를 내놓으라고 주장했다. 둘의 소송은 지루하게 이어졌고, 결국 구글은 패소했다. 이 과정에서 자바의 아버지인 제임스 고슬링은 오라클을 떠나 구글로 향하게 된다. IT 거인들이 출시한 프로그래밍 언어도 이러한 여러 의도에서 완전히 자유롭다고 말하기 어렵다. 특히 오픈소스 커뮤니티를 지원하는 이면을 들여다보는 작업은 매우 중요하다.

프로그래밍 언어 전쟁에 대한 개발자들의 시각은 조금씩 엇갈린다. 개발자 특유의 해커문화에서 비롯됐다는 데 대해선 이견이 없지만, 그 뒤에 숨겨진 전략에 대해선 서로 다른 의견을 내놓았다.

'생활코딩'이라는 프로그래밍 교육 프로그램을 운영하는 개발자 이고잉은 IT 거인들이 새로운 언어를 발표하는 배경에는 내부의 해커문화와 기술적 헤게모니 장악이라는 두 가지 측면이 공존하고 있다고 분석했다. 그는 “구글이든 애플이든 페이스북이든 해커들이 모인 집단”이라며 “하나의 해커문화로서 재미와 새로움, 혁신을 찾으려는 흐름이 결과로 나타난 것”이라고 말했다. 다만 오라클이 썬 마이크로시스템스를 인수한 배경에는 자바라는 프로그래밍 언어가 있었다는 점에 주목하면서 “기술적 헤게모니를 확보하기 위한 경쟁의 결과”라고 덧붙였다.

골빈해커라는 필명으로 유명한 김진중 호텔파인더 CTO는 개발자를 묶어두려는 록인 전략이나 헤게모니 경쟁과는 관련이 없다는 해석을 내놓았다. 그는 “현재 개발자들이 사용하고 있는 언어들은 이미 20년 이상 된 오래된 언어들”이라며 “언어의 패러다임이 바뀌고 있고 개발자들의 수도 늘어난 만큼 이를 지원해주기 위한 의도로 개발한 것으로 보인다”고 했다.

김진중 CTO는 현재는 프로그래밍 언어 자체보다는 프레임워크의 중요성이 더 커진 환경이라고 지적했다. 새로운 프레임워크 위에서 개발자들이 보다 편안하게 개발할 수 있는 환경을 제공하기 위해 새로운 언어를 내놓게 된다는 주장이다. 애플의 경우 LLVM, 코코아 프레임워크에서 보다 편리하게 개발 환경을 제공하기 위해 스위프트라는 언어를 출시했다고 보고 있다.

프로그래밍 언어 내엔 개발한 주체의 철학이 담겨 있고 의도가 숨어 있기 마련이다. 그 주체가 개인이냐 기업이냐, 국가냐에 따라 철학도 의도도 달라진다. 사적인 호기심과 해커정신으로 개발된 언어가 기업의 손으로 넘어가게 되면 어떤 모습으로 변모하게 되는지 이미 여러 사례를 통해 경험한 바 있다. 하나의 프로그래밍 언어의 성장사에는 복합적인 정치적, 경제적 이해관계가 얽혀 있다. 루비처럼 기존 언어가 만족스럽지 않다는 결핍으로부터 개발된 언어도 있지만, 코볼처럼 미국 국방부의 의도를 충족시키기 위해 개발된 언어도 존재한다. 자바스크립트를 견제하기 위해 마이크로소프트의 'J스크립트'가 나타났고, 신대륙의 포트란에 대항하기 위해 '알골 60'이라는 새로운 언어가 만들어지기도 했다. 스위프트, 고, 핵의 탄생을 늘 순수한 시선으로만 바라볼 수 없는 이유가 여기에 있다.


난 아무래도 조별과제하면 자료 조사는 진짜 잘할 거 같다..ㅋㅋㅋ

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글