C 연산자(C Operators) 원리 – C언어 강의 7

C 연산자 (C Operators)

연산자는 무엇일까요? 때로 이런 질문이 생길 수 있습니다.

연산자는 사칙연산의 덧셈, 뺄셈, 곱셈, 나눗셈 처럼 어떤 기능이라고 볼 수 있습니다. 하지만 연산자가 사칙연산만 있는 것은 아닌데요. 나중에 함수(function)를 배우고 나면 함수의 동작 방식도 연산자와 유사하다는 것을 알게 됩니다.

해서 나중의 혼돈을 피하기 위해 연산자를 정의해두는 것은 도움이 됩니다.

컴파일러 관점에서 보면 연산자는 미리 정의되어 있는 약속입니다. 컴파일 도중에 연산자를 만나면 그 기호에 매칭하는 연산(operate)을 실시하도록 정의되어 있습니다. 연산자는 데이터를 가지고 산술연산을 하거나 변수에 값을 할당하는 등의 일을 합니다.

C언어에는 사칙연산을 비롯해서 약 40여개의 연산자가 있습니다.

분류를 나누면 아래와 같은데…

  • 산술연산 (Arithmetic OP)
  • 관계/논리 연산 (Relational/Logical OP)
  • 비트연산 (Bitwise OP)
  • 할당연산 (Assignment OP)
  • 기타연산 (Misc OP)

이런 것들을 다 외우고 시작할 필요는 없습니다.

프로그래밍을 하면서 이런 연산자들이 필요한 상황이 있습니다. 외워서 코드를 작성하는게 아니라 코드를 작성하다보면 외워지는 것들이고 또 까먹으면 레퍼런스를 찾아 보면 되니까 암기를 하지 않아도 됩니다.

연산자의 원리

원리를 깨닫는 것은 중요합니다. 원리를 모르면 백날 기계적으로 코드를 작성할 뿐입니다.

예를 들어서 산술연산을 하나 해보겠습니다. 이 웹사이트의 특징은 원초적인 시각에서 출발하여 원리를 캐는 것 입니다. 다소 유치한 질문도 진지하게 생각하다 보면 나름의 깨달음을 얻는데 도움이 될 수 있습니다.

자, 우리가 컴퓨터를 가지고 덧셈을 한다고 칩시다. 덧셈을 하기 위해서는 무엇이 필요하지요? 숫자가 필요합니다. 덧셈은 일단 숫자가 두개는 필요합니다. 더하는 거니까. 이 두개의 숫자를 항이라고 하고 피연산자(Operand)라고도 합니다.

예를 들어 8와 2를 더하는 것은 두개의 항을 가지고 어떤 결과를 만들어 내는 일입니다. 여러가지 산술연산을 할 수 있습니다. 덧셈, 뺄셈, 곱셈 그리고 나눗셈까지 할 수 있습니다.

그것은 재료를 가지고 결과물을 만들어내는 것과 같습니다.

재료 8, 2 덧셈의 결과 10

재료 8, 2 뺄셈의 결과 6

재료 8, 2 곱셈의 결과 16

재료 8, 2 나눗셈의 결과 4

초등학교에서 산수를 배운 사람이라면 머리속에서도 충분히 계산할 수 있는 정도의 용량입니다. 한편 컴파일러는 어떨까요? 여기서 컴파일러는 이 소스코드를 처리하는 컴퓨터 그 자체입니다.

컴파일러는 8과 2 두개의 숫자만으로는 어떤 연산을 하는지 알수가 없습니다. 그래서 컴파일러에게 이 숫자를 가지고 무엇을 할 것인지 알려줘야 합니다. 이제 연산자 (Operator)가 필요하다는 것을 알 수 있습니다.

8 + 2 이렇게 생긴 수식을 C언어에서는 표현식 (Expression)이라고 합니다. 컴파일러가 소스코드에서 이 수식을 만나면 CPU를 사용해서 연산을 하도록 기계어 코드를 생성합니다.

CPU의 원리는 또 다른 영역이지만 대략적으로 설명하면 CPU는 자신의 작업을 실행하는 작업공간이 따로 있습니다. CPU가 사용하는 주요 메모리를 레지스터라고 합니다. 컴파일러가 작성한 기계어 코드는 정수 8과 2를 레지스터로 가져오고 덧셈을 하여 결과를 레지스터에 저장합니다.

정리하면 소스코드에 사용한 + 기호는 컴파일러에 의해 두 레지스터의 덧셈 연산을 지시하는 기계어로 번역되는 것 입니다. 이것이 연산자의 역할입니다. CPU에는 명령어 세트 (Instruction Set)이라는 기계어 코드 리스트가 있습니다. CPU의 제조사 마다 다르지만 인텔과 AMD의 PC를 사용한다면 보통 x86 Instruction Set에 호환되도록 설계 되어있습니다.

그렇다면 +, -, *, / 의 사칙연산 기호에 따라 기계어 코드가 다른 것도 알 수 있습니다.

사실 함수도 매개변수를 받아서 값을 처리한 후 결과값을 돌려주는 방식에서는 연산자와 비슷합니다. 그래서 연산자나 함수나 같은게 아니냐 그런 생각이 들 수도 있는데 함수는 코드가 한줄이 아니라 여러줄의 코드블록을 이루고 있고 스택메모리를 쓰는 등 내부의 작동방식이 다릅니다. 겉으로 보이는 것은 같지만 내부적인 데이터의 구조가 다르죠.

뭐 함수형 프로그래밍으로 유명한 하스켈의 경우 연산자와 함수를 동급으로 취급하기도 합니다. 하스켈은 연산자에도 타입클래스를 부여하는 등 원시적인 C언어에 비해서는 좀 차이가 나는 언어입니다. 초보자에게는 CPU 수준에서 원초적인 명령어에 가까운 심볼을 연산자라고 이해하면 충분합니다.

참고로 CPU에서 이루어지는 모든 연산은 0과1을 사용한 이진법으로 처리가 됩니다. 우리가 소스코드에 십진수를 적는 것은 사람이 보기 편하라고 쓰는 것 입니다. 컴퓨터는 어차피 십진수 코드를 2진수로 변환해서 봐야합니다.

때로는 이진수 계열로 데이터를 봐야할 때도 있는데,

C언어 코드에는 2진수 등 다른 진법도 사용할 수 있습니다. 하지만 이진수를 사용하면 너무 파일이 길어지니까 필요한 경우 2진수 계열인 16진수를 사용하는 경우도 많습니다.

이진수와 십진수는 배수로 호환이 안되지만 16진수는 2의 4제곱으로 호환이 가능하기 때문에 컴퓨터에서는 16진수도 많이 사용합니다.

산술연산 예제

그렇다면 이제 산술연산자 예제를 보겠습니다.

설명을 읽고 직접 코드를 작성해 보면 그전과는 조금 다르게 보일 것 입니다. 단순한 사칙연산도 컴퓨터 내부에는 복잡한 과정이 숨겨져 있는 법입니다.

아래의 예제에서는 정수형 변수에 산술연산의 결과를 저장했습니다.

#include <stdio.h>

int main(){

    int a, b, c, d;

    a = 8 + 2;
    b = 8 - 2;
    c = 8 * 2;
    d = 8 / 2;

    printf("a: %d\n", a);
    printf("d: %d\n", b);
    printf("c: %d\n", c);
    printf("d: %d\n", d);

    return 0;
}
a: 10
d: 6
c: 16
d: 4

산술연산자의 피연산자(Operand)는 숫자가 아니라 변수도 가능합니다. 아래와 같이 변수를 대상으로 연산할 수 있습니다.

여기서 a와 b는 int 형 변수입니다. 같은 타입끼리는 형변환이 없어도 연산이 잘 됩니다. 그런데 / 나누기에서 4에서 3을 나눈 값이 1입니다. 이는 정수형의 나눗셈 결과는 정수로 돌려주기 때문입니다. 정밀한 나눗셈 결과를 얻기위해서는 float 나 double 같은 부동소수점 타입을 사용해야 합니다.

정수의 나누기 결과를 보완하기 위해서 나머지 연산자가 있습니다. % 이는 나머지를 연산한 결과를 돌려줍니다. 4 나누기 3의 몫은 1이고 나머지는 1입니다. 이런 식으로 정수의 나누기도 결과를 완전히 얻을 수가 있습니다.

나머지 % 연산자는 루프 등 알고리즘에서 많이 사용하는 주요 연산자이기도 합니다.

#include <stdio.h>

int main(){

    int a, b;

    a = 4, b = 3;

    printf("a+b: %d\n", a+b);
    printf("a-b: %d\n", a-b);
    printf("a*b: %d\n", a*b);
    printf("a/b: %d\n", a/b);
    printf("a%b: %d\n", a%b);

    return 0;
}
a+b: 7
a-b: 1
a*b: 12
a/b: 1
ab: 1

요약

이번 포스팅에서는 C 연산자의 원리에 대해서 알아봤습니다. 연산자가 무엇인지 모르는 사람은 없습니다. 공교육을 받은 사람이라면 초등학교 때 기계적으로 산수문제를 풀어봤을 테니까요. 연산자나 산수 기호나 사람이 보면 다 똑같습니다.

그러나 컴퓨터에서 + 덧셈연산 하나를 하기 위해서는 소스코드를 기계어로 컴파일 하고 실제 CPU가 작업을 실행할 때 까지 수많은 단계를 거쳐야 합니다. 복잡함에 머리가 아파오지만 다행인 것은 다른 연산자들도 비슷하다는 것으로 한번 정도 원리를 이해하는 것으로 충분합니다.

컴퓨터 구조를 더 깊게 파고 싶으면 어셈블리어 쪽을 배우는 것이 좋습니다.

C언어를 학습하면서 스스로 주요 내용을 정리하는 것이 좋습니다. C언어는 프로그래밍도 중요하지만 원리적인 이해가 뒷받침되지 않으면 후반부로 갈 수록 급격하게 난이도가 높아지는 언어입니다.

이해가 안되면 다시 앞부분에 돌아와서 반복하는 수고 정도는 들여야 C를 마스터하는 보람이 있을 것 입니다. 이 튜토리얼 시리즈는 입문자용이지만 지금의 시대는 C언어는 완전히 입문자용 언어는 아닐겁니다.

과거에는 쓸만한 언어가 별로 없어서(파이썬이나 JS는 출시한지 오래되었지만 최근에서야 쓸만해졌음) C로 많이들 입문했던 것 같은데 그래서 C에서 그만둔 사람도 많았습니다.

어쨋든 C의 난이도는 여전하니까 좀 어렵고 적성적으로 안맞는다 싶으면 좀더 라이트하게 접근할 수 있는 언어인 파이썬이나 JS(웹개발용으로) 을 배운 다음에 C언어에 도전하는 것을 추천합니다.

외부참고문서

C Operators | Studytonight

Leave a Comment