brunch

매거진 Sinclair

You can make anything
by writing

C.S.Lewis

by Sinclair Feb 04. 2016

연산자와 예약어

C 프로그래밍은 산수다 I



전산학이라 불리는 Computer Sciences는 사실 수학의 한 분야로 시작했다고 합니다. 지금도 전세계 대부분의 대학에서 수학과의 한 파트로 존재하기도 합니다. 그래서 인지 순수하게 컴파일 하기 전의 프로그램의 내부를 들여다 보면 98%연산자와 2%예약어로 구성이 되어있다고 말해도 과언이 아닐 정도입니다. 그렇기 때문에 연산자를 잘 조합해서 적절히 사용하는 것이 멋지고 좋은 프로그램을 작성하는 기술이기도 합니다. 이제부터 C언어에서 사용하는 연산자와 문법의 기본이라고 할 수 있는 예약어를 살펴보겠습니다.

 

C언어의 연산자들은 몇 가지를 제외하면 대부분 저희가 중 고등학교에서 통해 배웠던 것과 같습니다. 하지만 그 몇 가지 다른 연산자들의 의미를 제대로 파악하지 못하게 되면 프로그램을 작성하는데 문제가 아주 많아집니다. 연산자들을 종류별로 설명한 것은 다른 책이나 인터넷 자료에 많이 있으니 참조하고 저는 우선순위를 기본으로 설명하겠습니다. 기본적으로 연산자의 의미와 우선순위를 잘 외어두면 프로그래밍의 사소한 논리적 오류를 줄여줄 뿐만 아니라 포인터와 다른 연산자들(특히 구조체나 배열연산자들)이 섞여 있는 복잡한 문장도 이해하기 쉬워집니다. 


포인터도 결국 연산자에 불과하니까요. 


실제로 제게 배운 것들 중에 가장 위대한 것은 연산자와 연산자 우선순위의 중요성이라고 말하는 분들도 있을 정도입니다.  



이 세상의 모든 연산자들은 의미를 갖습니다. 

의미 없는 인생이 없듯이 의미 없는 연산자는 없습니다. 


여러분들이 +연산자를 처음 만났을 때의 충격이 기억납니까? 저는 Integral기호(∫)를 처음 봤을 때의 충격이 아직도 생생합니다. 설마 여러분들 중 태어나면서부터 덧셈연산자를 알고 있는 사람이 있나요? 사칙연산이 익숙해진 지금 우리는 *의 의미를 압니다. 따로 설명하지 않아도 a*b라고 하면 누가 시키지 않아도 알아서 둘을 곱합니다. 아닌가요? 아무래도 저만 그런 듯 합니다. 저는 직업병의 일종인지 일반 산수문제를 풀면서도 자꾸 곱셈연산자를 ×가 아닌 *을 사용합니다. (그러면서 속으로 생각하겠죠. 이게 혹시 포인터인가?) 


연산자(operator)는 반드시 피 연산자(operand)와 함께 사용합니다. 


그래서 피 연산자를 한 개 필요로 하는 연산자를 단항 연산자(unary operator)라 부르고 피 연산자 두 개를 필요로 하면 이항 연산자(binary operator), 특이하게 피 연산자 세 개를 사용하는 경우 삼항 연산자(ternary operator)라 합니다. 이항 연산자가 가장 일반적인 경우이므로 여기에서 단항 연산자와 삼항 연산자라 지칭하지 않으면 이항 연산자입니다.   




C의 연산자들의 의미와 우선순위  



우선순위 0:  


( ) 괄호 연산자: 주로 연산자의 우선순위를 높여주는 일과 함수를 호출하는 용도로 사용합니다.  


[ ]
 배열 연산자: 배열을 만들고 메모리를 핸들링 하는 연산자로 배열에서 다시 한번 다룹니다.  


-> 포인터로 접근하는 구조체 멤버 연산자: 구조체를 포인터로 접근하게 되면 ->를 사용합니다. ->안에는 *와 .이 함께 들어있기 때문에 A->B라고 사용하면 (*A).B와 같은 의미가 됩니다. 구조체 이야기할 때 다시 한번 자세히 설명하겠습니다.  


.
구조체 멤버 연산자: 구조체는 서로 다른 타입의 데이터를 묶어서 사용하기 위한 C언어의 타입입니다. 구조체 안의 멤버를 접근하기 위해 사용합니다. 대부분의 컴파일러들은 구조체 연산자의 실행 속도를 높이기 위해 멤버들을 실제 사이즈가 아닌 지정 블록 단위로 저장합니다. 때문에 구조체를 사용할 때는 서로 다른 시스템 간의 구조체 교환은 특히 주의하셔야 합니다. 역시 구조체 부분에서 자세히 다루겠습니다. 

이 네 가지 연산자들은 반드시 기억해야 합니다. 어떤 연산자들보다 우선순위도 높을 뿐만 아니라 수학적인 연산자가 아니기 때문에 의미를 잘 기억해야 합니다. 특히 배열이 연산자이며 우선순위가 매우 높다는 걸 알아야 합니다.   



우선순위 0.7: 후행 증감 단항 연산자  


++ - - 뒤 증감 연산자: 밑에 있는 앞 증감 연산들과 같은 동작을 합니다. 사실 이 뒤 증감 연산자에 대한 부분은 대부분 책에 빠져있습니다. 심지어 저도 우선순위가 가장 낮은 줄 알고 그렇게 가르쳤던 적이 있었습니다. 하지만 오히려 앞 증감 연산자들보다 우선순위가 높다는 사실을 알게 되었습니다. 현재 많이 사용하고 있는 *pointer++라는 수식은 *(pointer++)와 같은 동작을 합니다. 괄호를 생략해도 의미가 바꾸지 않는 이유는 괄호 안의 연산자가 우선순위가 높기 때문입니다. a+b*c 와 a+(b*c)는 같은 의미인 것과 같습니다. 주의할 것은 우선순위가 높다는 것이지 증가되는 동작이 먼저 된다는 것은 절대로 아닙니다. 괄호를 사용 한 경우도 마찬가지 입니다. 실제 증가는 가장 나중에 하게 됩니다. '우선순위는 높고 동작은 가장 늦게' 이렇게 기억하면 됩니다. 다른 책에 설명이 없는 부분이라 우선순위를 그냥 0.7를 주었습니다. 관련된 예제는 문자열 처리 부분에서 한번 더 다루겠습니다. 여기에서는 우선 증감 연산자가 문장에서 단독으로 사용될 때는 피 연산자 앞에 있든지 뒤에 있든지 그 결과에 차이가 없지만 다른 연산자와 함께 사용되면 우선순위와 그 의미를 조심해야 한다는 것을 기억해야 됩니다. 



우선순위 1: 단항 연산자들  


! not 논리연산자: 참 거짓을 바꿔주는 단항 연산자 입니다. 다른 언어나 수학에서의 참 거짓과는 다르게 C언어에서는 참 거짓이 따로 존재하지 않습니다. 0이 거짓이고 0이 아닌 다른 모든 숫자는 모두 참입니다. 1도 참이고 1004도 참이고 -500도 참이고 3.141592도 참입니다. 때문에 !100값을 보면 0이 되며 !0값을 찍어보면 참의 대표 값인 1이 됩니다. 다른 언어로의 확장을 위해 0과 1로 define해서 사용하는 것이 좋은 방법이기는 합니다.  


~ 비트반전 연산자: 비트를 반전시키는 단항 연산자입니다. 비트(bit)는 binary digit의 줄임 말로 이진수를 말합니다. 컴퓨터가 이진수를 사용한다는 건 이제 널리 알려진 사실입니다. 컴퓨터가 사용하는 이진수의 0과 1을 서로 바꾸는 연산자입니다. 주로 보수를 만들기 위함입니다. 제가 지난 chapter에서 컴퓨터 구조에 대한 공부를 부탁 했습니다. 기억하죠? 컴퓨터 구조를 공부하면 비트 연산자들의 무한한 용도를 깨닫게 될 수 있을 것 입니다.  


* 포인터 연산자: 포인터 단항 연산자입니다. 여러 장에 걸쳐 자세히 설명 드리겠습니다. 부디 포인터를 두려워하지 마세요. 그저 덧셈이나 곱셈 같은 연산자에 불과합니다. 포인터에 관한 가장 큰 오류는 바로 포인터가 어렵다는 것입니다. 익숙해지면 덧셈하듯이 곱셈하듯이 포인터를 사용할 수 있습니다. 여기 제가 증인입니다.  


& 주소 연산자: 주소 단항 연산자입니다. 실제 메모리의 상대 주소 값을 알려 주는 연산자입니다. 주로 포인터 변수에 대입할 때 사용합니다.  


++ 앞 증가 연산자: 정수형 변수의 값을 1증가시키는 단항 연산자입니다. 그러면 그냥 1을 더하지 왜 ++이란 연산자를 만들었을까요? 그건 보통의 경우 +연산자는 컴파일러가 add로 번역하지만 ++은 increment로 번역하기 때문입니다. 일반적인 경우 컴퓨터 내부에서 add보다는 increment가 빠르다고 알려져 있습니다.  


- - 앞 감소 연산자: 정수형 변수의 값을 1감소시키는 단항 연산자입니다. 마찬가지로 decrement로 번역하게 되고 역시 일반적인 경우 컴퓨터 내부에서 increment 보다는 decrement가 빠르다고 알려져 있습니다. 때문에 순서가 중요하지 않다면 작은 숫자에서 ++를 사용하여 증가 시키는 것보다는 큰 숫자에서 --를 사용하여 감소시키는 것이 좋다고 합니다. 이런 것들은 컴퓨터 구조를 배우면 자세히 나와있습니다. 어때요? 공부해야겠죠?  


(type) 강제 형 변환 연산자: 일명 cast연산자라고 부르는 단항 연산자 입니다. 여러분은 캐스팅의 위력을 알고 있나요? 변경되는 데이터의 사이즈가 실제 데이터 사이즈보다 작게 되면 값은 강제로 잘리게 됩니다. 그렇지만 아무리 강제 형 변환이라고 해도 실제 데이터가 변경되는 일은 없습니다. 그냥 그런 타입의 데이터인 듯 동작하는 것입니다. 영화에 출연한 배우들이 실제 그런 영화처럼 사는 게 아닌 것처럼 말입니다. 캐스팅이 잘 된 드라마는 성공하고 그렇지 못하면 망하지 않습니까? 적절하고 멋진 캐스팅이 영화나 드라마의 완성도를 높이고 잘못된 캐스팅은 영화를 말아 먹기도 합니다. 마찬가지로 프로그래밍에서도 cast연산자는 매우 중요합니다. 프로그래밍 중 적절하게 잘 된 캐스팅은 프로그램을 아름답고 튼튼하게 만들지만 잘못된 캐스팅은 버그와 에러의 주범이 됩니다. 개인적으로 cast연산자를 얼마만큼 잘 사용하느냐가 고수와 하수를 나누는 기준이 될 수 있다고 생각합니다. cast연산자, 공부하세요!  


sizeof 메모리 바이트 크기 연산자: 데이터가 메모리에 차지하고 있는 공간의 실제 바이트 수를 알려 주는 단항 연산자입니다. 절대로 함수가 아닙니다. 대부분의 컴파일러가 sizeof 연산의 결과는 컴파일 하면서 상수 값으로 미리 정해 놓습니다. 그리고 중요하게 짚고 넘어가야 할 사실은 sizeof의 피 연산자가 대입, 증감 연산자나 함수 호출 등을 포함하는 수식일 경우 그 수식이 동작하지 않습니다. 이것에 대해 C 표준에서는 이렇게 얘기하고 있습니다. Constant expressions shall not contain assignment, increment, decrement, function-call, or comma operators, except when they are contained within a subexpression that is not evaluated. (ISO IEC 9899-1999 C99 Language Standard, 109 page)

  

    int datum = 100 ; 

    int size = sizeof(datum++) ; 

    printf("%d %d\n", datum , size) ; // 100 4 

    sizeof puts("***************************") ; 

    // 동작 안 해요~ 별들은 출력 안돼요.

     

 

- 음수 부호 연산자: 음수와 양수를 반전 시켜주는 negative 단항 연산자입니다.  


+ 양수 부호 연산자: 양수 부호 단항 연산자입니다. 














#Sinclair #씽클레어 #싱클레어 #씽클레어도씨 #씨언어 #씨프로그래밍  #C언어 #Cprogramming #C_Programming #C #Programming #Clanguage #C_Language

매거진의 이전글 연산자와 예약어
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari