C언어 산술연산자 / 대입연산자 – C언어 강의 8

C언어 산술연산자

산술연산자는 대부분 프로그래밍 언어에서 비슷한 기호를 사용합니다. 그래서 C언어의 산술연산자(Arithmetic operators)를 잘 익혀두면 자바, 파이썬, JS 등 다른 언어에서도 거의 비슷하게 사용할 수 있어서 적응이 쉽습니다. C언어 하나를 잘 배워두면 프로그래밍의 기초가 되는 것이지요.

우선 첫번째로 이해해야 할 부분은 프로그래의 연산자는 수학의 연산자와 다르다. 원리도 다르지만 기호도 조금씩 다릅니다. 이것은 처음 프로그래밍을 하는 사람은 반복된 실습을 통해서 몸적으로 적응해야 하는 부분입니다.

산술연산자 종류

C언어 산술연산자는 7개가 있습니다. 최근에 개발된 언어중에는 거듭제곱 연산자도 많이 추가가 되는데 C언어에는 제곱에 대한 연산자는 없고 수학 라이브러리에 있는 함수를 사용할 수 있습니다.

연산자내용형식
+두개의 피연산자를 더한다a+b
앞의 항에서 뒤의 항을 뺀다a-b
*두개의 피연산자를 곱한다a*b
/앞의 항을 뒤의 항으로 나눈다a/b
%나머지를 계산한다a%b
++증가연산자, 정수 1을 증가a++, ++a
감소연산자, 정수 1을 감소a–, –a
산술연산자

표를 보면 알겠지만 수학과 다른 점은 곱셈 * 나눗셈 / 나머지 % 증감연산자 ++, — 가 다릅니다. 반이상이 수학과는 다르네요.

산술연산자와 거의 함께 사용하는 대입연산자(assignment operator)를 알아본 후 예제를 통한 실습을 해보겠습니다.

대입연산자 종류

대입연산자 (Assignment Operator) 또는 할당연산자라고 하는데 기본은 = 입니다. =는 수학에서는 ‘같다’의 의미이지만 프로그래밍에서는 할당입니다. 수학의 같다를 표현하는 비교연산자는 = 기호를 두번 사용한 ‘==’ 입니다.

+= 같은 것은 증감연산자와도 비슷합니다. a++ 는 a+=1 과 최종 할당값이 같습니다. 다만 a++나 ++a는 순서에 따라 다른 결과를 얻기 때문에 차이가 있는데 여기서는 너무 tricky 하게 설명하지는 않겠습니다.

다른 언어와 비교를 해보면 파이썬 같은 언어는 증감연산자 a++,++a는 혼동만 부추긴다고 채택하지 않고 a+=1의 대입연산자만 사용하도록 되어 있습니다. 사실 a++ 의 방식도 for 루프에서 간편하게 사용가능한 장점이 있는데 막아버렸습니다. CPU의 명령어 세트에도 스스로를 증감시키는 increment 코드가 있습니다만 일부러 사용을 막아버린 거죠.

C언어에서는 같은 연산을 하는데 ++건 += 를 사용하건 자유긴 하지만… 웬만하면 코드를 읽기 편하게 하려면 한개를 정해놓고 쓰는게 좋습니다. ++ 쓰다가 += 도 쓰고 하면 읽는 사람 입장에서는 가독성이 떨어질 수 있습니다. C언어는 프로그래머들에게 최대한 자유를 주도록 설계되어 있습니다. 그렇다고 이것저것 다 가져다 쓰면서 코드를 복잡하게 하라는 것은 아닙니다.

최대한 자기의 스타일에 맞는 연산자를 코드 전체에 있어 통일성있고 가독성이 좋게 유지하는 것이 좋습니다. 어차피 본인이 언젠가 다시 읽어야 하는 코드입니다.

연산자내용표현식 예제
=우측 표현식의 피연산자를 좌측 변수에 할당한다a=b
+=우측의 피연산자를 좌측 변수에 더해서
그 결과를 좌측 변수에 할당한다.
a+=1
a=a+1
-=좌측 변수에서 우측 피연산자를 빼고
그 결과를 좌측 변수에 할당한다
a-=1
a=a-1
*=좌측 변수와 우측 피연산자를 곱하여
그 결과를 좌측 변수에 할당한다
a*=2
a=a*2
/=좌측 변수를 우측 피연산자로 나누고
그 결과를 좌측변수에 할당한다
a/=3
a=a/3
%/좌측 변수에 우측 피연산자로 나머지
연산을 한 후 결과를 좌측 변수에 할당한다
a%=2
a=a%2
대입연산자

연산자 용어 (lvalue/rvalue)

산술연산을 하면 어떤 결과값이 나옵니다. 그 결과값을 할당하는 것 까지가 보통 코드의 한 라인 정도가 됩니다. 연산을 해서 저장을 하고 또 그 값을 다시 그 다음 라인에서 활용해서 프로그래밍이 계속 진행이 됩니다.

그런면에서 프로그램이란 ‘연산 -> 결과를 대입 -> 결과를 가지고 다시 연산 -> 다시 결과를 대입’ 하는 무한 반복입니다.

이런 과정에서 빈번하게 사용하는 용어들이 있는데 용어를 아는 것이 곧 구조를 이해하는 것이므로 여기서 간단히 설명하도록 하겠습니다.

좌변값(lvalue)과 우변값(rvalue)

할당 연산자는 a = 3 + 4; 처럼 오른쪽 변의 표현식의 결과값을 왼쪽 변의 변수에 할당하는 것을 의미합니다. 여기서 a는 lvalue 3+4 표현식의 결과값을 rvalue 라고 합니다.

이렇게 보면 왼쪽의 변에 대입하는 것이기 때문에 변수가 되어야 합니다. 이 식을 3+4 =a; 로 바꾼다면 어떻게 될까요? a에 어떤 값이 들어있고 이것을 3+4의 결과값에 대입하는 것은 불가능합니다. 컴퓨터적으로 처리를 할 수는 있겠죠. 3+4의 값이 저장되어있는 메모리에다가 a에 들어있는 내용을 저장하면 되긴할 겁니다. 그러나 그것은 의미가 없죠.

변수는 말그대로 변하는 수를 저장하는 메모리 공간이고 3+4 같은 표현식의 결과는 리터럴 상수값으로 거기에 어떤 값을 저장할 수는 없습니다.

그래서 좌변 lvalue이 될 수 있는 것들은 변경할 수 있어야 합니다.(modifiable) 소위 객체(object)에는 데이터가 저장할 수 있으니까 변경이 가능한 객체가 항상 좌변값에 와야합니다.

좌변이니 우변이니 추상적인 이야기라서 처음 들으면 좀 와닿지 않을 수도 있습니다. C언어의 묘미는 적당히 내용을 모른체로 가다가 포인터와 메모리까지 학습한 후에야 그 동안 이해가지 않던 것들을 다시보면 이미 알고 있는 것입니다.

자세한 것은 포인터 챕터 포스팅에서 설명하겠지만 여기서는 대략의 설명만으로 넘어가겠습니다. a = 3+4; 에서 a는 변수일 수도 있고 포인터라고 하면 *a 일수도 있습니다. a라는 것도 결국 사람이 사용하도록 붙인 이름입니다. 실제 값이 저장되는 메모리 위치는 a라는 문자열과는 다릅니다. 결국 lvalue 라는 것은 값을 할당할 수 있는 실제의 메모리 위치를 가리키는 것이어야 한다. 는 말과 같습니다. 그외에 리터럴 상수건 뭐건간에 오른쪽 표현식의 결과값을 저장할 수 없는 값은 lvalue가 될 수 없습니다. 반면 우변값(rvalue)은 변경할 수 없어도 결과값이 있으면 상관없습니다.

이런 것을 상세하게 따지는 이유는 우변의 표현식은 임시값을 가지기 때문입니다.

예를 들어 a = (3 + 4) * 5 라고 하면 첫번째로 3+4의 결과가 CPU의 레지스터에 임시적으로 저장됩니다. 우변에 있다고 공중에 떠있는게 아니라 연산을 순서대로 실행합니다. 3+4의 임시값을 다시 5에 곱하여 35가 최종값이 나오는데 lvalue에는 이 최종값이 들어가게 되어있습니다. 때문에 = 할당연산자는 연산자의 우선순위에서 가장 밀려 있습니다.

중간 계산결과에 대해서는 어셈블리어를 사용해보면 이해할 수 있는데 C언어는 고수준 언어라서 그냥 사람이 쓰는 식처럼 (3+ 4) * 5 이렇게 막 쓰는 것 입니다. 내부적으로는 3+4를 한번 저장하고 다시 그것을 5와 곱해서 저장하는 과정을 거칩니다.

*연산자에는 우선순위가 있습니다. 수학에서 처럼 곱셈이 덧셈 연산자보다 우선순위가 높은 것이나 그런 서열이 있습니다. 잘 모르겠으면 ( ) 괄호를 치면 우선적으로 연산합니다.

연산자 예제

덧셈 연산자 +

덧셈 연산자(addition operator)는 연산자를 중심으로 양쪽에 있는 값을 더한 값을 돌려줍니다. 숫자같은 상수들 뿐만 아니라 변수를 더할 수도 있습니다. c = a + b; 의 뜻은 + 연산자를 중심으로 a와 b를 더해서 c에 대입(할당)하는 코드입니다.

lvalue 인 c에는 값을 할당할 수 있는 변수여야 하며 rvalue인 a+b의 결과값은 특정의 메모리 위치가 없는 임시값입니다. a나 b는 lvalue 값이 될 수 있지만 a+b는 lvalue가 될 수 없습니다.

#include <stdio.h>

int main(){

    printf("\n[+ operator]\n");

    printf("- decimal   : %d\n", 7 + 2);
    printf("- float     : %f\n", 5.0 + 3.2);
    printf("- e notation: %e\n", 9.0 + 12.875);

    int x, y;

    x=7, y=9;

    printf("\n[+ operator]\n");
    printf("x + y = %d\n", x+y);

    x=15, y=23;

    int z = x + y;
    printf("z = %d\n", z);
    
    return 0;
}
[+ operator]
- decimal   : 9
- float     : 8.200000
- e notation: 2.187500e+001

[+ operator]
x + y = 16
z = 38

뺄셈 연산자 –

뺄셈연산자(subtraction operator)는 – 기호를 중심으로 앞에 있는 수에서 뒤에 있는 수를 뺀 값을 돌려줍니다. 다른 설명은 덧셈과 같습니다.

#include <stdio.h>

int main(){

    printf("\n[- operator]\n");

    printf("- decimal   : %d\n", 7 - 2);
    printf("- float     : %f\n", 2.0 - 1.2);
    printf("- e notation: %e\n", 29.0 - 12.875);

    int x, y;

    x=17, y=9;

    printf("\n[- operator]\n");
    printf("x - y = %d\n", x-y);

    x=45, y=23;

    int z = x - y;
    printf("z = %d\n", z);
    
    return 0;
}
[- operator]
- decimal   : 5
- float     : 0.800000
- e notation: 1.612500e+001

[- operator]
x - y = 8
z = 22

부호연산자 +, –

부호연산자 +, – 도 연산자입니다. 부호연산자의 특징은 피연산자가 하나라는 것 입니다. -를 하면 부호를 반전시킵니다. +를 붙이면 결과적으로 아무것도 바뀌지 않지만 상징적으로 -의 반대적인 기호로 있습니다

-부호연산자가 하는 것은 부호의 반전입니다. 이렇게 하나의 피연산자를 가진 연산자를 단항연산자라고 합니다. 한편 덧셈 뺄셈을 하는 + – 는 피연산자(항)가 두개이기 때문에 이항연산자라고 합니다.

연산자가 작용하는 피연산자의 수에 따라 나누는 것인데 산술연산에서는 증감연산자 ++, — 도 단항연산자입니다. -(2+9) 처럼 단항과 이항연산자를 함께 사용할 수 도 있습니다. 생긴 것은 중학교때 풀던 수학문제와 비슷한데 CPU적으로 한번에 피연산자를 몇개를 가지고 처리하는냐에 따라 나누는 것 입니다.

    int x = 10;
    float y = -20.0;

    printf("+x:  %d\n", +x);
    printf("-x: %d\n", -x);
    printf("+y: %f\n", +y);
    printf("-y:  %f\n", -y);
+x:  10
-x: -10
+y: -20.000000
-y:  20.000000

곱셈과 나눗셈 연산자 * /

곱셈과 나눗셈 연산자 *과 / 도 숫자나 변수 모두 사용이 가능합니다. 덧셈이나 뺄셈과 마찬가지로 연산자의 좌우에 있는 피연산자를 대상으로 연산을 합니다.

곱셈은 딱히 어려운게 없지만 C언어는 제곱에 관한 연산자는 없다는 정도 입니다. 참고로 파이썬이나 하스켈 등에서는 ** 를 제곱의 연산자로 사용하고 있습니다.

나눗셈의 경우 사칙연산의 나머지 세개의 연산자와는 다른점이 있습니다. 첫째는 0으로 나눌 수 없습니다. 현실 수학에서도 어떤 숫자건 간에 0으로 나눌 수 없는 것 처럼 컴퓨터에서도 0으로 나눌 수 없습니다. 0으로 나눗셈을 시도하면 어느 컴파일러건 간에 division by 0 에러를 볼 수 있을 겁니다.

또한 나눗셈의 동작방식은 정수와 부동소수점 타입이 서로 다릅니다. 정수끼리 나누면 정수의 값이 나오는데 우리가 정수를 나누기할 때의 몫이 나옵니다. % 연산자로 나머지를 구할 수 있는데 정수의 나눗셈의 결과값은 몫과 나머지입니다. 반면 부동소수점 타입으로 나눗셈을 하면 정밀도 특정한 소수가 나옵니다. 현실 수학에서는 무한 소수 같은 것도 구하고 그러지만 컴퓨터안에서는 정밀도에 따라 표현됩니다. c언어에서는 IEEE 754로 float 형의 단정도와 double 배정도 두가지 방식이 있습니다.

부동소수점 수를 완벽하게 이해하는 것은 좀 더 복잡한 내용을 다루어야 하는데 처음부터 다 이해할 필요는 없습니다. 기본적으로 컴퓨터의 부동소수점 방식의 연산은 완전하지 않기 때문에 정밀한 프로그램을 만들어야 할 때 필요한 내용입니다.

#include <stdio.h>

int main(){

    int x, y;
    x = 35, y = 5;

    printf("\n[mutlipication operator *]\n");

    printf("x*y: %3d\n", x*y);
    printf("5*3: %3d\n", 5*3);

    printf("\n[division operator /]\n");
    printf("x/y: %3d\n", x/y);
    printf("10/2: %f\n", 10.0/2.0);

    return 0;
}
[mutlipication operator *]
x*y: 175
5*3:  15

[division operator /]
x/y:   7
10/2: 5.000000

나머지 연산자 %

나머지 연산자는 15 % 6 처럼 15에서 6을 나눈 나머지 즉 3을 돌려줍니다. 당연히 부동소수점 타입에는 적용되지 않습니다.

나머지 연산자는 괜히 있는 것 같지만 알고리즘에서 상당히 유용하게 자주 사용되는 연산자입니다. 이는 제어문 챕터에서 자세하게 설명할 내용입니다.

증감 연산자 ++, —

증감 연산자는 단항연산자이며 변수에만 사용가능합니다. 기호를 변수의 앞에 사용하냐 뒤에 사용하냐에 따라서 연산의 우선순위가 변경되기 때문에 대표적인 tricky 연산자입니다. 이것을 뭐 전위모드냐 후위모드냐 그러는데 너무 심각하게 생각할 필요는 없습니다.

++a 처럼 앞에 붙이면 그대로 실행하지만 a– 처럼 하는 경우 우선순위를 그 라인의 제일 마지막으로 미룹니다. 아래 예제 코드를 보면 뒤에다 연산자를 붙이는 경우 우선순위의 변경으로 다음 라인의 코드를 실행할 때쯤에야 데이터가 바뀌는 것을 볼 수 있습니다.

이런 까탈스러움 때문에 증감연산자의 사용을 선호하지 않는 프로그래머도 있습니다.

#include <stdio.h>

int main(){

    int a, b;
    a=7, b=11;
    printf("[Increment operator]\n");

    printf("[  a]: %d\n", a);
    printf("[++a]: %d\n", ++a);
    printf("[a++]: %d\n", a++);
    printf("[  a]: %d\n", a);

    printf("[Decrement operator]\n");

    printf("[  b]: %d\n", b);
    printf("[--b]: %d\n", --b);
    printf("[b--]: %d\n", b--);
    printf("[  b]: %d\n", b);

    return 0;
}
[  a]: 7
[++a]: 8
[a++]: 8
[  a]: 9
[Decrement operator]
[  b]: 11
[--b]: 10
[b--]: 10
[  b]: 9

대입연산자

대입연산자 = 에 대해서는 lvalue와 rvalue 에서 충분히 설명한 부분입니다. = 기호를 수학의 기호처럼 같다. 이렇게 보는게 아니라 컴퓨터 내부적으로 값이 저장가능한 메모리에 오른쪽의 표현식의 결과를 저장한다는 개념으로 이해할 필요가 있습니다.

입문과정의 간단한 문장에서는 몇개의 숫자를 저장하는 정도지만, 실제 프로그램에서는 여러 변수를 모아놓은 구조체나 객체에 여러개의 값을 대입연산자 하나로 저장합니다. 그래서 lvalue 와 rvalue 같은 작동원리를 잘 이해하는게 중요합니다.

요약

C 연산자로써는 기본이 되는 산술연산자와 대입연산자에 대해 조금 상세하게 알아봤습니다. C언어는 어렵지만 그만큼 컴퓨터 공부에 가치가 높은 언어입니다. 단순히 코드를 치고 실행하는 것으로도 좋지만 컴퓨터 시스템과 구조에 대해서 들여다보기 위한 도구로 가장 적합한 언어입니다.

컴퓨터 안에서 일어나는 일들은 너무 마이크로 하기 때문에 우리가 그 내용을 체감하기 어려운데 C언어에서는 CPU단위와 메모리 수준에서 바이트 수준으로 컨트롤을 해볼 수 있습니다. 코드와 씨름하는 것도 좋은데 그 보다는 컴퓨터 자체에 대한 다양한 서적과 동영상들을 보는 것을 추천합니다. 유튜브에는 프로그래머들의 인터뷰, 세미나, 강의 등 좋은 자료들이 많이 있습니다.

대부분 영어지만 어차피 코드도 영어로 짜는 거라 컴퓨터 공부를 한다면 영어를 공부를 해야 합니다. C언어는 리누스 토발즈 등 고인물들에게 인기가 많기 때문에 그들이 C에 대해서 어떻게 생각하는지 들어보는 것은 목적의식을 갖게 합니다.

리누스 토발즈 – C보다 나은 것은 없다

외부참고문서

Operators in C (programiz.com)

Operators in C – GeeksforGeeks

Leave a Comment