연산자와 조건문에 대한 얘기

2021. 10. 29. 00:18c,c++ 기본/next step

320x100
안녕하세요 대학생 개발자입니다.

이번 글에서는 연산자와 조건문에 대한 얘기를 간단히 해보려고합니다.

-----

cpu는 1,0 으로만 읽을 수 있는데 음수와 뺄셈을 어떻게 하는지..?

우선 이전 글에서 변수에 대한 언급을 했었습니다.

변수는 공간일 뿐이라고 말씀드렸었던 부분이 있는데요.

 

그저 공간밖에 존재하지 않는 상황에서 연산은 어떻게하며 뺄샘과 같은 처리는 어떻게 하는지 의문이실겁니다.

시작 전에 말씀드리지만 메모리에는 음수라는 개념이 존재하지 않습니다.

그저 1,0 만 존재하는 공간에 불과합니다.

이를 음수라고 정의하는건 사람이 한것이죠.

 

우선 시작하겠습니다.

 

+ 연산은 2진수에서도 10진수와 동일합니다

1111 + 1 = 10000 이죠

1010 + 101 = 1111 이죠

하지만 뺄샘같은 경우는 다릅니다. 2진수의 뺄샘... cpu는 뺄 샘을 하는 기능 자체가 없습니다. 무조건 더하기입니다.

그럼 더하기를 이용해서 뺄샘을 어떻게 구현한다는 말일까요?

9 - 6이 있습니다.

우리는 6의 보수를 구할겁니다. 4죠 그걸 9에다가 더합니다 그럼 13이 나오게 되는데 거기서 피연산자 중 우항  높은 자릿수 하나를 떨어뜨립니다. 그럼 3이 됩니다 이렇게 구하게 됩니다.

이걸 똑같이 보겠습니다

 

1010 - 101

10 - 5

2진수의 보수를 구하는 법은 간단합니다 ~(bit not) 연산을 취한 다음 1을 더하면 끝입니다.

그렇다면 1010의 보수는 101 + 1 -> 110이 되는거고 101의 보수는 10 + 1 -> 11이 되는 겁니다.

그럼 1010 에다가 11을 더합니다.

1101이 되겠군요 여기서 최고 비트 1개를 떨어뜨립니다. 그럼 101이 됩니다

답은 5가 맞죠? 이런 식으로 구하게 된답니다...

뭐 설명이 예시가 들어가게 돼서 좀 허접해 보이긴 하는데 cpu는 뺄 샘이라는 연산이 불가능하기 때문에 이런 방식을 쓴다 정도로만 이해해주셔도 좋을 것 같습니다.

 

음... 비트 연산도 매우 중요합니다.

하지만 속도가 빨라지게 하기 위해서 곱셈, 나눗셈 연산을 쉬프트 연산으로 쓰거나 이런 행동은 하지 마세요 어차피 티 도안 나는 시간 차이가 나는 거니까요.

 

그리고 sizeof연산자인데요... 이건 그냥 상수 치환 문입니다.. 그냥 들어온 타입에 대해서 사이즈를 뱉어내는 기능입니다. 그 이상도 이하도 아닙니다..

 

그리고 if문에 대한 얘기를 좀 해보려고 합니다

if문에 논리 연산자가 들어가게 된다면

&& 연산자의 경우에는 false가 나올 확률이 높은 것을 앞에 두는 것이 좋습니다. 왜냐면 앞이 true인데 뒤가 false이면 연산 낭비니까요

반대로 || 연산자의 경우에는 true가 나올 확률이 높은 것을 앞에 두는 것이 좋습니다. 위와 똑같은 이유로 앞이 false인데 뒤가 true가 나오면 앞의 연산은 시간낭비가 되니까요...

 

 

그리고 if문의 신기한 점은 우리는 분명 조건을 a == b라고 걸지만 컴파일러는 반대로 해석한다는 점 알고 계신가요?

a와 b가 다르면 if문을 스킵하는 방향으로 어셈블리를 뽑아준답니다~ 정말 신기하죠ㅋㅋㅋ 허허... 저만 신기한가요

a < b 이면 a>= b를 체크한 다음 스킵하는 방향으로 어셈블리를 뽑아버린다는 거...\

사진에서 보시면 jne jump not equal의 약자입니다.

동일하지 않으면 그냥 점프해서 스킵해버려라라고 어셈블리를 뽑아주는군요 정말...

네 뭐 그렇다고요 다음으로 넘어가겠습니다

 

 

많은 분들이 if -> else if -> else if -> else 문이 좋으냐 아니면 switch가 좋으냐에 대해서 궁금해하시더라고요.

음... 결론부터 말씀드리자면 switch가 더 좋을 확률이 있습니다라고 말씀을 드려야 될 것 같습니다.

 

이유가 if문을 쭉 길게 나열하게 된다면 cmp jump cmp jump cmp jump를 통해 계속 분기를 타는 문장이 나오게 되는데요...

switch 같은 경우에는 case의 인덱스를 이쁘게 잘 짜 놓으신다면

switch문에서 jump table을 만들어 버리고 offset + jumptable의 인덱스만큼 점프를 한 값으로 바로 확 뛰어 버린답니다.

그러니까 엄청난 분기문을 타던 케이스를 한 번의 연산으로 처리를 해버리는 거죠

이런 코드가 있다면 어셈블리는 다음과 같이 뽑힐 것이고 메모 리뷰도 함께 보겠습니다.

1번째 실험케이스
1번째 실험 케이스
2번째 실험 케이스

 

자 보이시나요..?

메모리에 예쁘게 그려진 무언가가 보이죠..?

혹시 모르실까 봐 싶어서 어셈블리 옆에 코드 주소까지 달려있게 해 놨는데...

 

케이스를 1~60까지 넣었더니 jump 할 곳들을 담는 메모리를 가지고 있고

00~ 09와 0a가 포함된 1바이트의 배열 60칸이 생겼습니다.

그 밑에 어떤 값이 나오면 오프셋에 이 값을 더해서 바로 점프하라는 메모리 배열들이 있습니다.

이 값은 스위치 문에 있는 값 중 가장 작은 값을 0번으로 생각하고 그 뒤로부터 값이 매겨지는 식으로 증가합니다.

offset + jump tbl의 [0] 값을 더한 곳으로 점프하라라고 하는 것입니다...

반대로 저 스위치 문에 없는 2를 넣게 되면 0a라는 값이 나오게 되는데

총 스위치 문의 개수는 10개 인덱스로 따지면 9죠 그래서 10을 넣게 되면 스위치 문을 빠져나가는 주소로 점프를 해버린답니다... 저런 식으로 만들어져 있어요 그렇다면 여기서 한 가지 짚고 넘어가야 될 점

 

안 되는 케이스도 분명히 있을 겁니다. 만약 인덱스가 255가 넘어버린다면 1바이트 안에 담을 수가 없으므로 점프 테이블 인덱스가 의미가 없어질 겁니다. 그래서 인덱스가 255개를 넘지 않으면서 동시에 255 이상의 값이 없으면서 가지런히 놓여 있다면 switch문이 더 효율적일 것입니다. enum을 쓰실 때 개수가 255개 이하라고 한다면 if else 분기를 타는 것보다 훨씬 더 효율적이다 라는 결과를 도출해낼 수 있겠습니다.

 

그래서 상황에 맞게 switch문을 잘 쓰시면 if else문 보다 빠르지만 위에서 말씀드렸던 그 조건에 부합하지 않는다면 switch문도 결국 cmp jmp cmp jmp를 반복하면서 if else if~~... else 문과 동일한 결과를 나타내게 되므로 결론은

 

스위치 문을 이쁘게 잘 짜자입니다.. ㅎㅎ

 

 

오늘은 여기까지 하도록 하겠습니다. 유익한 정보가 되었으면 좋겠습니다. 이번에도 긴 글 읽어주셔서 정말 감사드립니다. 저는 다음 내용을 준비해서 또 오도록 하겠습니다 그럼 안녕히 계세요!!

 

 

320x100