지난 글에서는 산술 연산과 그 결과에 따라 발생하는 Condition Code 의 종류에 대해 정리하였다.
산술 연산에 대표적으로 add 와 sub 가 있는데, 명령어에 cc 옵션(?)을 붙인 addcc, subcc 명령어는 연산 후 condition code를 반환한다.
Condition Code에는 Z, N, V, C 4가지가 있었으며, Z는 연산 결과가 0인지 판별하고, N 은 연산 결과가 음수인지 판별한다.
V는 오버플로우 여부를 판별하고, C는 캐리를 반환한다.
이번 글에서는 4가지 코드 중 V 와 C 에 대해 조금 더 자세하게 정리해보고자 한다.
그리고 이를 위해 먼저 Unsigned Integer 와 Signed Integer 에 대해 정리해보겠다.
Unsigned Integer / Signed Integer
어셈블리언어에서의 데이터는 데이터의 자료형이 C언어와 같이 Int, long, char 로 구분되지 않는다.
그저 32bit 로 표현되는 데이터 그 자체이다.
32bit 를 쭉 나열하였을 때, 제일 왼쪽에 있는 (제일 번호가 큰 31번째) 비트를 Most Significant Bit 라고 하여 MSB 라고 하고, 제일 오른쪽에 있는 (제일 번호가 작은 0번째) 비트를 Least Significant Bit 라고 하여 LSB 라고 부른다.
Unsigned Integer 는 말 그대로 부호가 없는 정수로서, 0부터 2**32-1 사이의 수를 표현할 수 있다.
Signed Integer 는 부호를 위한 비트가 하나 필요하므로 절댓값의 표현 범위로 보면 범위가 더 넓은 셈이다.
Signed Integer 는 부호를 표시하기 위해 MSB 비트를 이용한다.
MSB 비트가 0이면 0또는 양수, 1이면 음수로 여긴다.
또한 이진수에서는 음수를 표현할 때 2의 보수를 이용한다.
2의 보수는 기존 이진수의 비트를 모두 뒤집은 뒤, 1을 더하여 표현한다.
그리고 CPU는 add, sub 연산을 수행할 때, Signed 와 Unsigned 를 구분하지 않는다.
MSB 비트값은 원래대로 계산하되, 그 의미가 부호이냐 숫자 자릿수냐의 차이일 뿐이다.
Carry
Carry는 Unsigned Integer 간 연산시 의미를 갖는 코드이다.
또 의미가 add 와 sub 에 따라서 다르다.
먼저 서두를 깔고 가자면
add 연산시 Condition Code 의 C 부분은 아래와 같은 의미를 가진다.
C = 1 : 오버플로우 발생
C = 0 : 오버플로우가 발생하지 않음.
이 부분은 어렵지 않게 이해할 수 있다.
32 bit 로 표현할 수 있는 자리가 넘어가는 연산결과가 발생하면 Carry bit가 여분으로 발생한다.
오히려 뺄셈을 할 때는 (Signed 연산) Carry가 발생한 부분을 버림으로서 정확한 연산 결과를 얻게 되었다.
이제 sub 연산시의 의미를 알아보자.
sub 연산 시 Condition Code 의 C 부분은 아래와 같은 의미를 가진다.
A - B 의 결과로
C = 0: A >= B
C = 1: A < B
컴퓨터는 뺄셈 연산을 할 때, 빼는 수에 - 부호를 붙인 뒤 덧셈을 한다.
이를 컴퓨터 식으로 다시 표현하면, 빼는 수에 대해 2의 보수를 취한 뒤, 그 수를 빼지는 수에 더한다.
위 이미지는 6 - 3 을 계산하는 과정이다.
6 - 3 은 6 + (-3) 과 같으므로 먼저 3에 대해 2의 보수를 취한다.
그 결과를 더하면 위 이미지와 같이 Carry 가 발생한다.
2의 보수를 취하면 원래 3과 그 보수화된 수의 합이 0이 된다.
(엄밀히는 0이 아니라 32bit의 범위 밖에 있는 수, 256이 된다. 이를 32bit 공간에 표현하면 0이다.)
따라서 3의 보수를 unsigned integer로 읽으면 256 - 3 = 253 과 같다.
만약 빼지는 수가 3보다 큰 수라면, 3의 보수의 unsigned Integer 의미인 253 과 3이상의 수를 더하는 순간 반드시 Carry가 발생한다.
>> 그러면 3보다 C가 활성화 되었다는 의미는 '3보다 크다' 가 아니라 '3보다 크거나 같다' 여야 하지 않나?
아 강의록에도 A >= B 로 되어있었네..ㅎ
따라서 C를 통해 연산 결과 어떤 수가 더 큰 지 비교를 할 수 있게 된다.
(이전 강의록의 예제 코드에서 비교시 subcc 를 사용하는 이유가 이것이었다.)
뺄셈시 C 값을 저장할 때는 Carry 부분의 비트를 반전시켜 저장한다.
따라서 Carry가 발생하면 1 -> 0 을 저장하고, 발생하지 않았다면 0 -> 1 을 저장한다.
Overflow
오버플로우 (V) 는 signed Integer 간 연산시 의미를 갖는 코드이다.
의미에 대해 서두를 던져보면 아래와 같다.
V = 0, N = 0 이면, 연산 결과는 양수
V = 0, N = 1 이면, 연산 결과는 음수
즉, V = 0 일 때는 N (negative) 의 정보를 그대로 사용할 수 있다.
이 내용은 직관적으로 이해할 수 있다.
overflow 라는 문제가 발생하지 않았으니, N 이 표현하는 정보를 곧이 곧대로 사용할 수 있는 것은 자명하다.
V = 1, N = 0 이면, 연산 결과는 음수
V = 1, N = 1 이면, 연산 결과는 양수
즉, V = 1 일 때는 N 의 정보를 반대로 해석해야한다.
하지만 이 부분은 잘 와닿지 않는다.
왜 V 가 활성화 되었을 때는 N의 정보를 반대로 해석해야 할까?
우선 오버플로우가 발생했는지 판별하는 방법부터 생각해보자.
오버플로우가 발생하면 다음과 같은 상황이 발생했다는 의미다.
양수와 양수를 더했는데, 결과의 MSB가 음수를 가리키거나,
음수와 음수를 더했는데, 결과의 MSB가 양수를 가리키는 상황이다.
(뺄셈은 부호를 거꾸로 해서 더한다고 보면 덧셈과 같다.)
따라서 양수와 양수를 더했는데, MSB가 음수를 가리킨다면 실제 결과는 양수이고, 오버플로우가 발생했음을 알 수 있다.
음수와 음수를 더했다면 MSB가 음수를 가리켜야 하지만, 양수를 가리키고 있다면 실제 결과는 음수여야 함이 자명하고,
오버플로우가 발생했음을 알 수 있다.
V = 1, N = 0 이면, 연산 결과는 음수
V = 1, N = 1 이면, 연산 결과는 양수
이제 이 내용을 다시 보면 이해가 어렵지 않다.
V = 1 이 되었다는 의미는 음수와 음수를 더했는데 양수가 나왔거나, 양수와 양수를 더했는데 음수가 나온 상황이다.
만약 N = 0 이라면 MSB가 0 이라는 의미이므로, 음수와 음수를 더했는데 양수가 나온 상황이다.
그렇다면 '원래 기대되는 연산의 결과' 는 음수인 것이 맞으므로 연산 결과가 음수이다.
양수도 마찬가지다
N = 1 이 되었다는 의미는 결과값으로 음수가 나왔다는 뜻인데, V = 1 이라면 오버플로우가 발생해서 음수가 나온 상황이다.
그렇다면 원래는 양수와 양수를 더해서 양수라는 결과를 기대했는데 음수가 나온 상황인 것이므로, 실제 연산결과는 양수가 되었어야 함을 알 수 있다.
처음에는 N = 0 인데 어떻게 음수고, N = 1 인데 어떻게 양수지? 하고 생각했는데, 오버플로우가 발생했기 때문에 의도와 다른 결과가 나왔다고 보니 이해가 되었다.
이번 글에서는 cc 옵션(?)이 붙는 명령어에 대해 정리해보았다.
다음 글에서는 x 옵션(?)이 붙는 명령어에 대해 정리해보면서 32bit 범위를 넘어가는 큰 수 연산을 어떻게 하는지 정리해보겠다.
'CS > 어셈블리' 카테고리의 다른 글
[SPARC] 14. 곱셈과 나눗셈 그리고 서브루틴 (2) | 2023.10.11 |
---|---|
[SPARC] 13. Hardware & 큰 수 연산 (0) | 2023.10.11 |
[SPARC] 11. 산술 연산과 Condition Code (0) | 2023.10.05 |
[SPARC] 10. SPARC에서 사용하는 메모리 종류 (0) | 2023.10.04 |
[SPARC] 9. Assembly Language Programming (0) | 2023.09.30 |