[만화가 있는 C] 7. 문장 vs. 표현식

7. 문장(statement) vs. 표현식(expression)

  우리는 10장 ‘제어구조’에서 흐름flow을 제어하는 5개의 문장(if, switch, for, while과 do…while)에 대해서 살펴볼 것입니다. 하지만, 이 장의 설명을 위해 먼저 if-문(if-statement)을 간단하게 소개합니다. if문은 괄호 안에 명시된 조건을 비교하여 연관된 문장의 실행 여부를 결정합니다. 예를 들어 아래의 문장을 봅시다.

if (i>j) printf("i is greater than j");

  위 문장은 괄호 안에 명시된 i>j가 만족된다면 printf()를 실행하고, i>j가 만족되지 않는다면 printf()를 실행하지 않습니다. 우리가 유의해야할 사항은 if는 다른 문장을 포함한 1개의 문장이라는 것입니다. 즉, 위의 문장은 1개의 문장, if-문장 입니다.

  if-문은 다음과 같은 문법을 가집니다.

        if (비교문장1)

          문장1;

        [else if (비교문장2)

          문장2;][…]

        [else

          문장3;]

  if 문은 첫 번째 비교문장1을 검사하여, 문장이 참true이면, 문장1;을 수행합니다. 첫 번째 비교문장1이 거짓이면, else if에 명시된 비교문장2 등을 차례대로 검사하여, 문장이 참인지를 검사합니다. 검사의 결과가 참이면, 문장2;를 수행합니다. 명시한 모든 비교문장들이 거짓이면, 마지막 else에 명시된 문장3을 수행합니다.

  우리는 else if와 else를 둘러싼 ‘[‘와 ‘]’에 주목해야 합니다. 이것은 이러한 문장이 생략가능(option)함을 나타냅니다. 그러므로 else if와 else구조는 생략 가능합니다. 또한 else if의 마지막에 명시된 […]를 주목하세요. 이것은 else if문장이 0번 이상 여러 번 반복(repetition)될 수 있음을 의미합니다. 이러한 표기법을 사용하는 것은 복잡한 문법을 체계적이고 형식적(formal)으로 설명할 수 있으므로, 많이 사용합니다.

 cmd.exe를 실행해서 명령햄 창을 실행한 다음, ‘path /?’를 입력해 결과를 확인해 보세요. 이러한 명령들의 사용법은 위에서 제시한 표기법으로 표기하고 있다. 수직 바(vertical bar, |)는 혹은(or)을 의미합니다.

  이제 몇 가지 if문의 예제를 통해 복잡한 if문을 연습해 봅시다. 아래 프로그램의 출력 결과는 얼마일까요?

#include <stdio.h>

void main() {
    int i=2,j=3;

    if (i>j)//①
        i=i+1;
        if (i<j)//②
            j=j+1;
    else//③
        printf("%d\n",i);
}

  보다 크다greater than 연산자 ‘>’는 수학에서와 같은 의미를 지닌다고 가정하면, ①번 문장에 의해 i>j라는 조건문장은 결과가 거짓입니다. 그러므로 ③번 else문이 실행되어 화면에 2가 출력될 것을 기대합니다.

  하지만, 직접 실행해 보면, 화면에는 아무 것도 인쇄되지 않습니다. 아래의 문장에 대한 대답이 그 이유를 설명해 줍니다.

“③번의 else문이 ②번 if와 짝 지워진 것인지, ①번 if와 짝 지워진 것인지 어떻게 알 수 있는가요?”

  이제 소스를 아래와 같이 배열해 봅시다.

#include <stdio.h>

void main() {
    int i=2,j=3;

    if (i>j)//(1)
        i=i+1;
    if (i<j)//(2)
        j=j+1;
    else//(3)
        printf("%d\n",i);
}

  이러 애매한 문제를 해결하는 규칙을 우리는 이해해야 합니다. 어느 if에 걸려있는지dangling 애매한 else문제를 달랑거리는 else문제(dangling else problem)이라고 합니다. 대부분의 프로그래밍 언어는 이러한 문제를 해결하는 같은 규칙을 사용하고 있으며, 규칙은 다음과 같습니다.

“else의 위쪽으로 봐서, else와 짝 지워지지 않은 가장 가까운 if 혹은 else if와 짝 짓습니다.”

  이 규칙에 의하면, (3)번의 else는 (2)번과 짝 지워집니다. 그러면 (1)번 if문이 만족되지 않으면 실행할 문장은 없습니다. 그래서, 화면에는 아무 것도 출력되지 않습니다. (3)번의 else가 (1)번과 짝 지워지도록 하는 방법은 없을까요? 그것은 (2)번의 if문장을 하나의 독립된 문장으로 만듦으로써 가능합니다.

Q. 블록(block)이 뭐죠?

A. C/C++ 언어의 구조에 의해 여러 개의 독립된 문장을 1개의 문장 취급해야할 필요성이 빈번하게 생깁니다. 구조적 언어structured programming language는 이러한 기능을 위해서 블록block 기능을 제공합니다. 예를 들면 Pascal 언어에서는 블록을 begin…end로 표현합니다. C언어는 블록을 나타내기 위해 {…}를 사용합니다. 즉, {와 } 사이에 명시된 2개 이상의 블록은 1개의 문장 취급됩니다. 블록은 실제로 몇가지 추가적인 특징을 가집니다. 이러한 사항은 후에 자세하게 설명하겠습니다.

  블록block을 이용하면 여러 문장을 하나의 문장 단위로 만드는 것이 가능합니다. 블록(block, ‘{‘와 ‘}’에 둘러쌓인 문장들)은 여러 개의 문장을 하나의 문장으로 만들어 줍니다. 그러므로 원래의 프로그래밍 의도대로라면 아래와 같이 블록을 사용하여 소스를 수정해야 합니다. 이제 화면에는 2가 출력될 것입니다.

#include <stdio.h>

void main() {
    int i=2,j=3;

    if (i>j) {//①
        i=i+1;
        if (i<j)//②
            j=j+1;
    } else//③
        printf("%d\n",i);
}

  이제 본론으로 들어가 봅시다. 컴파일러는 if의 비교문장이 참인지를 어떻게 검사할까요? 먼저 문장statement과 표현식expression을 명확히 구분할 필요가 있습니다. 문장 자체의 값이 C에서 지원하는 데이터 값 – 일반적으로 정수 – 인 경우, 이러한 문장을 표현식이라고 합니다. 표현식은 문장입니다. 하지만, 문장은 표현식이 아닙니다. 그러므로 if의 괄호 안의 ‘비교문장’은 엄격하게는 틀린 표현입니다. 문장은 결과 값으로 참(true)이나 거짓(false)을 가지지 않을 수도 있습니다. 다음의 예들에서 문장과 표현식을 구분해 봅시다.

    int i=2,j=3;//(1)

    i+j;//(2)

    i=2+3;//(3)

    i=j=2;//(4)

    return i;//(5)

  값을 가지지 않는 문장은 모두 2개((1)과 (5))입니다. 표현식은 나머지 3개입니다. 컴파일러가 (2)번 문장을 컴파일하지 못하는 것은 아닐까요? 아닙니다.

  표현식은 문장 자체가 값을 가지는 것에 주목하세요. 그러므로, 문장을 등호의 오른쪽에 서서, 왼쪽에 대입 가능하다면 그 문장은 값을 가지므로 표현식입니다.

  (1)번 문장을 고려해 봅시다. int i=2,j=3;은 값이 아닙니다. 그러므로 아래의 문장은 가능하지 않습니다.

k=(int i=2,j=3);

  (5)번 문장 마찬가지입니다. return i;는 i란 값을 리턴하는 문장이지만, 문장 자체가 값을 가지지는 않습니다. 그러므로 k=(return i);는 불가능합니다. 나머지 (2),(3)과 (4)는 모두 표현식입니다.

  i+j가 숫자임에 주목하세요. 그러므로, k=(i+j);는 가능합니다. 또한 i=2+3이 표현식, 즉 숫자임에 주의하세요. 이 문장은 2+3을 i에 대입하여, i의 값을 5로 만들며, 마지막에 대입된 값, 즉 5가 이 문장의 값이 됩니다.

 수학에서는 이것은 표현식이 아닙니다. 수학에서 대입문assignment statement은 숫자가 될 수 없습니다.

  그러므로 i가 5이므로, 문장의 값이 5가 아니라, i=2+3이 표현식이 숫자 5인 것입니다. 그러므로 i=j=2;란 문장은 2를 j에 대입하고, j의 값을 i에 대입하는 것이 아닙니다. i=j=2;라는 문장의 의미는 다음과 같습니다.

“2를 j에 대입하고, j=2를 i에 대입합니다. 물론 j=2는 2입니다.”

  아래에 다시 쓴 문장은 우리가 등호가 포함된 표현식을 이해하는데 도움이 될 것입니다.

i=(j=2);

  C 언어를 배운 많은 사람들이 이러한 문장을 잘못 이해하고 있습니다. 그렇다면 이제 if문의 문법을 정확하게 새로 적어 보겠습니다.

        if (표현식1)

          문장1;

        [else if (표현식2)

          문장2;][…]

        [else

          문장3;]

  위와 같은 if-문의 구조에서, if 괄호 안의 표현식이 ‘참이다’, ‘거짓이다’의 판단기준은 무엇일까요? C에는 참/거짓이란 값은 존재하지 않습니다. 컴파일러는 참과 거짓을 다음과 같이 구분합니다.

 하지만, C++에는 bool형이 추가되었으며, false, true가 키워드로 제공됩니다. 하자만, false,true는 여전히 정수 호환되는 값을 가지며, 그것은 각각 0,1입니다.

  (1) 숫자 표현식 0은 거짓입니다.

  (2) 0이외의 모든 표현식은 참입니다.

  그러므로, 1은 참입니다. 100, -100, -1은 모두 참입니다. 0만이 거짓입니다. 0을 2의 보수로 표현했을 때, 모든 비트가 0이 됨에 주목하세요. 0이외의 정수는 2의 보수 표현에서 적어도 1비트가 1임에 주목하세요. 컴파일러는 참과 거짓을 다음과 같이 구분합니다.

“표현식의 결과, 모든 비트가 0이면 거짓입니다. 최소한 한 개의 1인 비트를 가지면 참입니다.”

  그렇다면, 우리가 위에서 첫번째 예로 든 소스에서 i>j가 표현식, 즉 결과가 0혹은 다른 숫자란 말인가요? 그렇습니다. 이것이 이 장에서 말하려고 하는 핵심입니다.

  컴파일러는 관계 연산relational operation의 결과를 판단하기 위해, i>j에 대해, i-j를 수행합니다. 결과를 검사하여, 모든 비트가 0이면, i>j를 0으로 평가합니다. 그렇지 않으면, i>j를 1로 평가합니다. 즉, i>j라는 문장은 결과가 0혹은 1인 표현식인 것입니다. 그러므로 컴파일러가 if (i>j) 문장;을 검사할 때 괄호안의 조건이 참이기 때문에, 문장1;을 수행하는 것이 아니라, 괄호안의 값이 0이 아니기 때문에 문장1;을 수행하는 것입니다.

  이러한 관계 연산자에 대한 이해는 우리가 관계 연산자의 결과를 자유롭게 이용하여 프로그래밍이 가능하도록 합니다. i와 j값을 비교하여, i>j인 경우는 k에 1을 대입하고, 아닌 경우는 0을 대입하는 문장이 필요하다고 가정합시다. 우리는 if문을 사용하여 아래와 같이 소스를 작성할 수 있을 것입니다.

    if (i>j)

        m=1;

    else

        m=0;

하지만, 관계 연산자의 특징을 이용하면, 다음과 같이 문장을 간단히 쓸 수 있습니다.

m=(i>j);

  i가 j보다 크면 1을 리턴하는 함수를 만든다고 가정해 봅시다. 역시 다음과 같은 문장을 사용하여 함수를 구성할 수 있습니다.

return i>j;

  printf(“abc”);라는 함수 호출은 사실 표현식입니다. printf()는 자신이 출력한 문자의 갯수를 리턴합니다. 그러므로 아래 문장의 출력결과는 abchello3입니다.

printf(“hello%d”,printf(“abc”));

  마지막으로 아래 프로그램의 출력결과가 무엇이 될지 각자가 풀이해 보시기 바랍니다.

#include <stdio.h>

void main() {
    int i=2,j=3,k=4;

    printf("%d,%d,%d,%d\n", i>j, i==j, j!=k, i<=k);
}

  이 장에서 말한 주제를 다시 한 번 정리하면 다음과 같습니다.

“C에서의 모든 제어구조 – if, while, do… while, switch, for – 의 문장에서 사용하는 비교 문장은 그 문장이 수학적인 참true을 검사하는 것이 아닙니다, 그 문장의 값이 0이 아닌지를 비교합니다. 비교 문장이 1이기 때문에 실행되는 것도 역시 아닙니다. 문장의 결과가 0이 아니기 때문에 – 1인 경우도 포함하여 – 조건 검사가 참이 되는 것입니다.”

문장의 종류

  C언어의 문장의 종류는 다음과 같습니다. 표현식은 문장의 부분집합subset입니다. 각 문장은 설명이 필요할 때 자세하게 설명할 것입니다. 우리가 아래의 문장의 종류에서 지금까지 다룬 것은 표현식과 선언문이 전부입니다.

  C++에는 좀 더 추가적인 문장이 있습니다. 이러한 주제는 ‘만화가 있는 C++’에서 다루도록 하겠습니다.

■ 라벨문(labeled statement)     : switch에서 혹은 goto의 대상으로 사용됩니다.

■ 표현식(expression statement)  : 문장의 결과가 값value입니다. 대부분의 수학 문장과 몇 특수한 문장을 포함합니다.

■ 복합문(compound statement) : 블록을 의미합니다.

■ 선택문(selection statement)   : if, switch문이 있습니다.

■ 반복문(iteration statement)    : for,while,do…while문이 있습니다.

■ 분기문(jump statement)       : break, continue와 goto문이 있습니다. goto문은 이 책에서 설명하지 않습니다.

■ 선언문(declaration statement) : 변수선언, 함수선언문 등이 있습니다. 다른선언문은 앞으로 배우게 될 것입니다.

■ try-블록문(try-block statement): ‘만화가 있는 C++’에서 자세히 다룹니다.


실습문제

1. 대입문은 표현식이기는 하지만, 함수의 파라미터로 사용하는 것은 (가능하지만) 추천되지 않습니다. 이유가 무엇일까요? 구체적인 예를 들어 설명하세요(힌트: 호출 관례calling convention).

2. 아래 문장의 출력결과가 나오는 과정을 상세하게 설명하세요.

printf("hello%d",printf("abc"));

@

만화가 있는 C

Leave a Reply