본문 바로가기
Programming/C Programming Modern Approach (K.N.K)

[KNK 정리] 2장: C Fundamentals

by hyeyoo 2021. 8. 16.
※ 이 블로그의 글은 글쓴이가 공부하면서 정리하여 쓴 글입니다.
※ 최대한 내용을 검토하면서 글을 쓰지만 틀린 내용이 있을 수 있습니다.
※ 만약 틀린 부분이 있다면 댓글로 알려주세요.

요약/정리

간단한 C언어 프로그램의 구현

#include <stdio.h> /* Standard Input/Output 관련된 함수의 정의를 포함한다 */

/* main 함수: 모든 C 언어 프로그램에 들어가는 필수적인 함수 */
int		main(void)
{
	printf("To C, or not to C: that is the question.\n"); /* 문자열 출력 */
	return 0; /* main 함수를 종료한다. 0은 프로그램이 정상적으로 종료됨을 의미한다. */
}

 

전처리(Preprocess), 컴파일(Compile), 링크(Link)

C언어로 프로그램을 만들때 보통 전처리, 컴파일, 링크라는 세 단계를 순서대로 거친다.

전처리

전처리는 컴파일 이전에 #define, #include, #if, #endif와 같은 명령어들을 사전에 처리하는 과정이다. 따라서 전처리를 수행하는 전처리기는 C언어로 작성된 코드를 입력으로 받으며, 출력도 C언어로 작성된, 전처리가 끝난 코드이다.

컴파일

컴파일은 C언어로 작성된 코드를 기계어(object code)로 변환하는 과정이다. 하지만 컴파일이 끝났다고 해서 바로 완성된 프로그램이 되는 건 아니다.

링크

컴파일이 된 기계어 코드와 데이터를 하나로 묶어서 실행가능한 프로그램으로 합치는 과정이다. 링크까지 끝나야 프로그램이 완성된다.

C언어로 작성된 프로그램의 구조

/* 지시어들 (directives) */

int main(void)
{
	/* 구문들 (statements) */
}

위의 소스를 보면 C언어로 작성된 프로그램은 지시어, main 함수, 구문으로 구성되어있다.

 

지시어 (directive)

지시어는 컴파일 전에, 전처리기에 의해서 처리된다. 전처리기가 사용하는 명령어들을 지시어라고 한다. 맨 처음에 작성했던 코드에서는 #include <stdio.h>가 '전처리기에게 stdio.h를 포함하라는' 지시어다. 모든 지시어는 항상 #(샵)으로 시작한다.

함수 (function)

함수는 '일련의 구문들(statements)에 이름을 붙인 것'이라고 할 수 있다. 다르게 말하면 특정 목적을 달성하기 위한 코드의 모음을 함수라고 할 수도 있겠다. 함수는 프로그램을 구성하는 구성 요소이다.

 

C언어에는 다양한 함수가 존재하지만 main 함수만큼은 필수로 존재해야 한다. (MAIN도, mAiN도 안되고 main이어야 한다.) main 함수는 프로그램이 시작할 때 가장 먼저 실행되는 함수이다. 그리고 main 함수는 리턴값이 존재한다. 0을 리턴하는 것은 프로그램이 문제없이 종료되었다는 것을 의미하며, 0이 아닌 값을 리턴하는 것은 프로그램이 비정상적으로 종료되었다는 것을 의미한다. 이 리턴값은 운영체제에게 전달된다.

 

main 함수에서 리턴을 하지 않는 경우에는 C89의 경우, 알 수 없는 값이 리턴된다. (undefined) C99에서는 main 함수에 한에서 0이 리턴된다. (C99도 main 함수가 아니라면 undefined)

구문 (Statement)

구문은 프로그램이 실행될 때 수행되는 명령어이다. return 구문도 하나의 구문이고, 함수 호출도(위에서 본 printf()같은) 하나의 구문이다. C언어에서 모든 구문은 세미콜론(;)으로 끝나는데, 예외적으로 복합 구문(Compound Statement)는  세미콜론으로 끝나지 않는다. 구문을 세미콜론으로 끝나게 하는 이유는 하나의 구문이 여러 행에 걸쳐서 작성될 수 있기 때문에, 어디가 끝인지 명시해주기 위함이다.

 

구문과 달리 지시어는 세미콜론으로 끝나지 않고, 일반적으로 한 행을 차지한다.

주석 (Comment)

주석은 코드를 설명하기위해 사용된다. 주석은 여러 줄 주석 (/* */)과, 한 줄 주석 (//)이 있다. 한 줄 주석은 C89에는 존재하지 않고, C99부터 존재한다.

 

여러 줄 주석은 /*로 시작해서 */으로 끝난다. 첫 예제에서 사용한게 여러 줄 주석의 예시이다.

	return 0; /* main 함수를 종료한다. 0은 프로그램이 정상적으로 종료됨을 의미한다. */
/*
이
주석은
여러
줄이다
*/
/*
 * 이렇게 형식을
 * 갖춰서 쓸 수도 있다
 */

주의할 점은, 여러 줄 주석은 중첩해서(nested) 사용할 수 없다. 아래의 예시는, 주석이 시작하지 않았는데 주석이 끝났으므로 컴파일 에러가 난다.

/* 
	주석
    /* 중첩된 주석 */
    
*/ <-- 컴파일 에러!

한 줄 주석은 //로 시작하며, 라인이 끝날 때 주석도 끝난다.

return 0; // main 함수를 종료한다. 0은 프로그램이 정상적으로 종료됨을 의미한다.

참고로 여러 줄 주석을 코드를 비활성화하는 데에 사용하는 것은 좋은 방법이 아니다. 왜냐하면 비활성화하는 코드에 주석이 포함되는 경우 골치아파지기 때문이다. 그럴 때는 나중에 14장에서 알아볼 전처리기의 조건부 컴파일(Conditional Compilation)을 사용하면 된다.

 

변수와 할당

변수의 타입 (Type)

변수는 용도에 따라서 서로 다른 형태의 데이터를 저장한다. 이때, 알맞는 타입을 사용하여 변수를 선언할 수 있다.

 

변수의 선언 (Declaration)

변수를 사용하려면 그 전에 먼저 선언(Declare)해야 한다.

형식 : (변수의 타입)  (변수의 이름)

예시:

int age;
float amount;

같은 타입의 변수는 한꺼번에 선언할 수 있다.

int age, height, weight;

변수를 선언할 때 주의점은, C89의 경우 함수의 맨 처음에만 변수를 선언할 수 있다.

/* 지시어들 (directives) */

int main(void)
{
    /* 선언문들 (declarations) */
    /* 구문들 (statements) */
}

예를 들어 C89는 변수를 선언하려면 아래와 같이, 선언을 먼저 하고 그 다음 사용해야 한다.

/* directives */
#include <stdio.h>

int main(void)
{
    /* declarations */
    int x;
    
    /* statements */
    x = 10;
    printf("%d\n", x);
}

하지만 C99에서는 꼭 그럴 필요가 없고 아래와 같은 코드도 허용된다.

#include <stdio.h>

int main(void)
{
    int x;
    
    x = 10;
    int y = 20;
    printf("%d + %d = %d\n", x, y, x + y);
}

변수의 초기화

변수는 사용하기 전에 초기화해야한다. 초기화란 변수를 사용하기 전 맨 처음 변수에 값을 할당하는 것을 의미한다. 만약 초기화하지 않은 변수에 접근하면 그 동작은 정의되지 않는다. (undefined)

 

아, 참고로 전역변수와 같이 static storage duration에 해당하는 변수들은 명시적으로 초기화되지 않는 경우 0으로 초기화된다. C89에선 다음과 같이 명시하고 있다.

If an object that has static storage duration is not initialized explicitly, it is initialized implicitly as if every member that has arithmetic type were assigned 0 and every member that has pointer type were assigned a null pointer constant.

 

int x = 10;

여기서 변수 x를 초기화하는 10을 initializer라고 한다. 이때 initializer는 변수마다 각각 존재한다. 따라서

int x, y, z = 10;

위의 코드에서는 x, y는 초기화되지 않았으며 z만 10으로 초기화되었다.

 

상수 (Constant) 정의하기

상수를 정의하는 방법은 두 가지 방법이 있다. 하나는 const 키워드를 이용하는 것이고 하나는 매크로로 정의하는 것(macro definition)이다. 

 

매크로 정의는 define이라는 지시어를 이용하며 다음과 같이 사용할 수 있다.

#define PI 3.141592

단, 매크로가 하나의 값이 아니라 expression이라면 반드시 괄호로 감싸주어야 한다.

#define MUPLICATIVE_INVERSE_OF_PI (1 / 3.141592)

const 키워드를 변수 앞에 붙여도 상수를 정의할 수 있다.

const float PI = 3.141592;

식별자 (Identifier)

C언어에서 변수, 함수, 매크로 등은 이름이 필요하다. 이런 이름을 식별자 (identifier)라고 하며, C에서는 식별자에 대한 규칙이 있다.

 

- 영문 대소문자, 숫자, 언더스코어(_)로 구성된다.

- 식별자의 첫 번째 글자는 대소문자 또는 언더스코어로 시작해야 한다.

- 식별자는 case-sensitive하다.

- 키워드는 식별자로 사용할 수 없다.

 

키워드의 종류는 다음과 같다. 빨간 색은 C99에 추가된 키워드이다.

auto, enum, restrict, unsigned,

break, extern, return, void,

case, float, short, volatile,

char, for, signed, while,

const, goto, sizeof, _Bool,

continue, if, static, _Complex,

default, inline, struct, _Imaginary,

do, int, switch, double,

long, typedef, else, register,

union

 

참고사항

- C89에서는 첫 31자, C99에서는 첫 63자가 동일한 경우 식별자간 구분이 불가능할 수도 있다. (표준상)

- C99에서는 영문 대소문자 외에도 universal character name를 식별자로 사용할 수 있다.

C 프로그램의 구조

C 프로그램은 토큰의 나열로 생각할 수 있다. 토큰은 그 뜻을 바꾸지 않고는 더이상 분리할 수 없는, 문자의 나열을 의미한다. 예를 들어서 int height; 라는 구문에서 height를 hei ght라고 분리하게 되면 의미가 바뀌기 때문에, height가 하나의 토큰이라고 할 수 있다.

 

	printf("hello, world\n");

위의 구문에선 printf, (, "hello, world\n", ), ;가 모두 각각의 토큰이며, '(', ')', ',', ';'은 punctuation (구두점/문장부호)이라고 한다.

 

일반적으로 토큰 사이에 공백(whitespace, tab, new line)의 개수는 중요하지 않다. 가독성은 떨어지지만 아래처럼 쓸 수도 있다. 

	printf          
	        (  
	                "hello, world\n"            
	                                    )         ;

토큰 사이의 공백의 개수가 중요하지 않으므로, 몇 가지 짚고 넘어갈 특징이 있다.

- 필요하다면 하나의 구문은 여러 라인에 걸쳐 작성될 수도 있다.

- 가독성을 위해 x*y대신 x * y처럼 연산자 사이에 공백을 붙이거나 printf("%d %d",x,y); 대신 printf("%d %d", x, y);처럼 콤마 뒤에 공백을 붙일 수도 있다.

- 들여쓰기를 통해서 가독성을 높일 수 있다.

- 텅 빈 행으로 프로그램을 논리적으로 분리할 수도 있다.

 

주의사항

- 토큰과 토큰 사이에는 공백을 넣을 수 있지만, 토큰 안에는 공백을 넣을 수 없다.

- string literal 안에는 개행 문자(new line character)를 넣을 수 없다. 대신 \n을 써야한다.

연습문제

1. Create and run Kernighan and Richie's famous "hello, world" program:

#include <stdio.h>

int	main(void)
{
	printf("hello, world\n");
}

Do you get a warning message from the compiler? If so, what's needed to make it go away?

 

gcc로 컴파일해본 결과 경고는 뜨지 않았다. 아무래도 main 함수에서 return문이 없는 경우 경고가 뜨는 것을 묻는듯 하다. 경고를 없애려면 return 0;을 추가하면 된다.

 

2. Consider the following program:

#include <stdio.h>

int	main(void)
{
    printf("Parkinson's Law:\nWork expands so as to ");
    printf("fill the time\n");
    printf("available for its completion.\n");
    return 0;
}

(a) Identify the directives and statements in this program.

directives:

#include <stdio.h>

statements:

    printf("Parkinson's Law:\nWork expands so as to ");
    printf("fill the time\n");
    printf("available for its completion.\n");

 

(b) What output does the program produce?

output:

Parkinson's Law:
Work expands so as to fill the time
available for its completion

 

3. Condense the dweight.c program by

dweight.c:

/* Computes the dimensional weight of a 12" x 10" x 8" box */

#include <stdio.h>

int	main(void)
{
    int height, length, width, volume, weight;
    
    height = 8;
    length = 12;
    width = 10;
    volume = height * length * width;
    weight = (volume + 165) / 166;
    
    printf("Dimensions: %dx%dx%d\n", length, width, height);
    printf("Volume (cubic inches): %d\n", volume);
    printf("Dimensional weight (pounds): %d\n", weight);
    
    return 0;
}

 

(1) replacing the assignments to height, length, and width with initializers and

Answer:

/* Computes the dimensional weight of a 12" x 10" x 8" box */

#include <stdio.h>

int	main(void)
{
    int height = 8, length = 12, width = 10, volume, weight;
    
    volume = height * length * width;
    weight = (volume + 165) / 166;
    
    printf("Dimensions: %dx%dx%d\n", length, width, height);
    printf("Volume (cubic inches): %d\n", volume);
    printf("Dimensional weight (pounds): %d\n", weight);
    
    return 0;
}

 

(2) removing the weight variable, instead calculating (volume + 165) / 166 within the last printf.

Answer:

/* Computes the dimensional weight of a 12" x 10" x 8" box */

#include <stdio.h>

int	main(void)
{
    int height = 8, length = 12, width = 10, volume;
    
    volume = height * length * width;
    
    printf("Dimensions: %dx%dx%d\n", length, width, height);
    printf("Volume (cubic inches): %d\n", volume);
    printf("Dimensional weight (pounds): %d\n", (volume + 165) / 166);
    
    return 0;
}

 

4. Write a program that declares several int and float variables - without initializing them - and then prints their values. Is there any pattern to the values? (Usually there isn't)

 

초기화되지 않은 변수는 출력하면 아무 값(쓰레기값)이나 나오며, 일반적으로 나오는 값에는 패턴이 없다. 실제로 C 표준 상에서 초기화되지 않은 변수에 접근하는 것은 정의되지 않는다. (Undefined Behavior) 초기화되지 않은 변수에는 프로그램이 이전에 사용했다가 반환된 메모리의 데이터가 들어있을 수도, 운영체제로부터 페이지를 넘겨받았을 때 그대로의 값이 들어있을 수도 있다.

 

5. Which of the following are not legal C identifiers?

(a) 100_bottles

(b) _100_bottles

(c) one__hundred__bottles

(d) bottles_by_the_hundred_

 

답은 (a)이다. 식별자(identifier)는 숫자로 시작할 수 없다.

 

6. Why is it not a good idea for an identifier to contain more than one adjacent underscore (as in current__balance, for example)?

 

그렇게 쓸 이유가 없어서 생각은 안해봤는데, 폰트에 따라서 언더스코어가 한 개인지 두 개인지 구분이 안될 것 같다.

 

8. Which of the following are keywords in C?

(a) for

(b) If

(c) main

(d) printf

(e) while

 

답은 (a), (e)이다. (c), (d)는 함수의 이름이므로 식별자이지 키워드가 아니고, (b)는 If가 아니라 if이다.

 

8. How many tokens are there in the following statement?

answer=(3*q-p*p)/3;

 

answer, =, (, 3, *, q, -, p, *, p, ), /, 3, ;

14개이다.

 

9. Insert spaces between the tokens in Exercise 8 to make the statement easier to read.

 

answer = (3 * q - p * p) / 3;

 

10. in the dweight.c program, which spaces are essential?

 

new line character는 그대로 두고 tab, space를 모두 제거해보면 아래와 같은 코드가 된다.

주석, string literal은 space를 제거하면 의미가 변하므로 제거하지 않았다.

/* Computes the dimensional weight of a 12" x 10" x 8" box */

#include <stdio.h>

int	main(void)
{
int height=8,length=12,width=10,volume;
volume = height*length*width;
printf("Dimensions: %dx%dx%d\n",length, width,height);
printf("Volume (cubic inches): %d\n",volume);
printf("Dimensional weight (pounds): %d\n",(volume+165)/166);
return 0;
}

Programming Projects

1. Write a program that uses printf to display following picture on the screen:

       *

      *

     *

*   *

 * *

  *

 

#include <stdio.h>

int	main(void)
{
    printf("       *\n");
    printf("      *\n");
    printf("     *\n");
    printf("*   *\n");
    printf(" * *\n");
    printf("  *\n");
    
    return 0;
}

 

2. Write a program that computes the volume of a sphere with a 10-meter radius, using formula v = 4/3 * PI * r * r * r. Write the fraction 4/3 as 4.0f/3.0f. (Try writing it as 4/3. What happens?) Hint: C doesn't have an exponetiation operator, so you'll need to multiply r by itself twice to compute r^3.

#include <stdio.h>

int	main(void)
{
    int radius, volume;
    const int PI = 3.141592;
    const float constant = 4.0f/3.0f;
    
    radius = 10;
    volume = constant * PI * radius * radius * radius;
    
    return 0;
}

4.0f/3.0f를 4/3으로 계산하면, int와 int를 나누면 해당 expression도 int형이기 떄문에, 1.33333...이 아니라 1이 나오게 된다.

 

3. Modify the pgoram of Programming Project 2 so that it prompts the user to enter the radius of the sphere.

#include <stdio.h>

int	main(void)
{
    int radius, volume;
    const int PI = 3.141592;
    const float constant = 4.0f/3.0f;
    
    scanf("%d", &radius);
    volume = constant * PI * radius * radius * radius;
    
    return 0;
}

4. Write a program that asks the user to enter a dollars-and-cents amount, then displays the amount with 5% tax added:

Enter an amount: 100.00

With tax added: $105.00

#include <stdio.h>

int	main(void)
{
    float amount;
    
    printf("Enter an amount: ");
    scanf("%f", &amount);
    printf("With tax added: $%f", amount * 1.05);

    return 0;
}

5. Write a program that asks the user to enter a value for x and then displays the value of following polynomial:

3x^5 + 2x^4 - 5x^3 - x^2 + 7x - 6

Hint: C doesn't have an exponetiation operator, so you'll need to multiply x by itself repeatedly in order to compute the powers of x. (For example, x * x * x is x cubed.)

 

#include <stdio.h>

int	main(void)
{
    int x, answer;
    
    scanf("%d", &x);
    answer = 3 * x * x * x * x * x;
    answer += 2 * x * x * x * x;
    answer -= 5 * x * x * x;
    answer -= x * x;
    answer += 7 * x;
    answer -= 6;

    return 0;
}

6. Modify the program of Programming Project 5 so that the polynomial is evaluated using following formula:

((((3x + 2)x - 5)x - 1)x + 7)x - 6

Note that the modified program performs fewer multiplications. This technique for evaluating polynomials is known as Horner's Rule.

#include <stdio.h>

int	main(void)
{
    int x, answer;
    
    scanf("%d", &x);
    answer = 3 * x + 2;
    answer = answer * x - 5;
    answer = answer * x - 1;
    answer = answer * x + 7;
    answer = answer * x - 6;

    return 0;
}

7. Write a program that asks the user to enter a U.S. dollar amount and then shows how to pay that amount using the smallest number of $20, $10, $5, and $1 bills:

 

$20 bills: 4

$10 bills: 1

 $5 bills: 0

 $1 bills: 3

 

Hint: Divide the amount by 20 to determine the number of $20 bills needed, and then reduce the amount by the total value of $20 bills. Repeat for the other bill sizes. Be sure to user integer values throughout, not floating-point numbers.

 

#include <stdio.h>

int	main(void)
{
    int amount;
    
    scanf("%d", &amount);

    printf("$20 bills: %d\n", amount / 20);
    amount -= 20 * (amount / 20);

    printf("$10 bills: %d\n", amount / 10);
    amount -= 10 * (amount / 10);
    
    printf(" $5 bills: %d\n", amount / 5);
    amount -= 5 * (amount / 5);
    
    printf(" $1 bills: %d\n", amount);

    return 0;
}

8. Write a program that calculates the remaining balance on a loan after the first, second, and third monthly payments:

 

Enter amount of loan: 20000.00

Enter interest rate: 6.0

Enter monthly payment: 386.66

 

Balance remaining after first payment: $19713.34

Balance remaining after second payment: $19425.25

Balance remaining after third payment: $19135.71

 

Display each balance with two digits after the decimal point. Hint: Each month, the balance is decreased by the amount of the payment, but increased by the balance times the monthly interest rate. To find the monthly interest rate, convert the interest rate entered by the user to a percentage and divide it by 12.

 

#include <stdio.h>

int	main(void)
{
    float loan, interest_rate, monthly_payment, balance;
    
    printf("Enter amount of loan: ");
    scanf("%f", &loan);
    
    printf("Enter interest rate: ");
    scanf("%f", &interest_rate);
    /* convert it from yearly to monthly */
    interest_rate = 0.01 * interest_rate / 12;
    
    printf("Enter monthly payment: ");
    scanf("%f", &monthly_payment);
    
    balance = loan;
    balance = balance - monthly_payment + balance * interest_rate;
    printf("Balance remainig after first payment: $%.2f\n", balance);

    balance = balance - monthly_payment + balance * interest_rate;
    printf("Balance remainig after second payment: $%.2f\n", balance);

    balance = balance - monthly_payment + balance * interest_rate;
    printf("Balance remainig after third payment: $%.2f\n", balance);
}

댓글