컴퓨터가 관계 연산자를 처리하는 방법

rofs·2024년 6월 24일
0

관계 연산자는 두 값을 비교하는 데 사용된다. 관계 연산자에는 아래와 같은 연산자가 있다.

  • == (같다)
  • != (같지 않다)
  • > (크다)
  • < (작다)
  • >= (크거나 같다)
  • <= (작거나 같다)

컴퓨터가 관계 연산자를 처리하는 방법

1. 문법 분석 및 토큰화

관계 연산자가 포함된 코드를 작성하면, 컴퓨터는 먼저 문법을 분석하고 코드를 토큰화한다. 소스 코드는 코드의 요소를 나타내는 일련의 토큰으로 변환된다.

예를 들어, a > b라는 표현식은 세 부분으로 토큰화된다.

  • a (변수)
  • > (관계 연산자)
  • b (변수)

2. 의미 분석

토큰화가 완료된 후, 컴파일러나 인터프리터는 의미 분석을 수행하여 연산이 유효한지 확인한다. 여기에는 ab의 타입이 > 연산자와 호환되는지 확인하는 작업이 포함된다. 예를 들어, ab는 일반적으로 숫자 타입이거나 비교 가능한 타입이어야 한다.

3. 중간 표현

그 다음 단계는 표현식을 중간 표현(IR*)으로 변환하는 것이다. 이는 실제 하드웨어보다 추상화된 낮은 수준의 코드이다. 예를 들어, a > bCMP a, b와 같은 중간 언어 명령어로 번역될 수 있다.

*IR: Intermediate Representation. 컴파일러가 소스 코드를 기계어로 번역하는 과정에서 사용하는 중간 단계의 표현 형식. IR은 컴파일러가 다양한 최적화와 코드 생성 작업을 수행할 수 있게 하는 중요한 역할을 한다.

4. 코드 생성

중간 표현은 기계어로 번역된다. 기계어는 CPU의 아키텍처에 따라 다르지만 비교를 처리하는 특정 명령어가 포함된다.

예를 들어, x86 어셈블리 언어에서:

  • CMP 명령어는 두 값을 비교하는 데 사용된다.
  • CMP 명령어 다음에는 JG (크면 점프), JL (작으면 점프), JE (같으면 점프) 등과 같은 조건부 점프 명령어가 비교 결과에 따라 사용된다.

예시:

CMP eax, ebx ; eax와 ebx 레지스터의 값을 비교
JG label     ; eax가 ebx보다 크면 label로 점프

5. 실행

실행 중에 CPU는 다음 단계를 수행한다:

  1. 피연산자 가져오기: ab의 값을 레지스터나 메모리에서 가져온다.
  2. 비교 연산: CPU는 산술 논리 연산 장치(ALU)를 사용하여 비교를 수행한다. ALU는 비교 결과에 따라 프로세서의 상태 레지스터에 특정 플래그를 설정한다(예: 제로 플래그, 부호 플래그, 오버플로 플래그).
  3. 플래그 설정:
    • 제로 플래그(ZF): 비교 결과가 0일 때(예: a == b)
    • 부호 플래그(SF): 결과가 음수일 때(예: 정수 비교에서 a < b)
    • 캐리 플래그(CF): 부호 없는 비교일 때
    • 오버플로 플래그(OF): 정수 비교에서 오버플로가 발생했을 때

Let’s learn about CMP instruction

CMP 명령어는 두 피연산자를 비교하는 데 사용된다. 이 명령어는 피연산자를 뺄셈하여 플래그 레지스터의 값을 설정하지만, 실제 피연산자의 값은 변경하지 않는다. 이를 통해 조건부 분기 명령어와 함께 사용되어 프로그램의 흐름을 제어할 수 있다.

CMP 명령어의 사용법

CMP 명령어의 일반적인 형식은 다음과 같다:

CMP destination, source

여기서 destinationsource는 비교할 두 피연산자다. destination에서 source를 빼고 그 결과를 버린다. 대신, 상태 플래그 레지스터의 플래그가 설정된다.

플래그 레지스터

CMP 명령어는 상태 플래그 레지스터의 여러 플래그를 설정한다. 주요 플래그는 다음과 같다:

  • ZF (Zero Flag): destination - source의 결과가 0일 경우
  • SF (Sign Flag): destination - source의 결과가 음수일 경우
  • CF (Carry Flag): destinationsource보다 작을 경우(부호 없는 비교)
  • OF (Overflow Flag): 부호 있는 연산에서 오버플로가 발생할 경우

6. 분기 (해당되는 경우)

조건부 연산(예: if (a > b) { ... })의 경우 CPU는 비교에 의해 설정된 플래그를 사용하여 코드의 다른 부분으로 분기할지 여부를 결정한다.

예를 들어:

CMP eax, ebx ; eax와 ebx를 비교
JG greater_label ; 크면 greater_label로 점프

7. 결과 반환

비분기 컨텍스트에서(예: result = a > b와 같은 표현식), 비교 결과는 일반적으로 불리언 값(true 또는 false)으로 레지스터나 메모리 위치에 저장된다.

고수준 언어의 예시

다음 C++ 코드를 고려해 보자:

int a = 5, b = 3;
bool result = a > b;
  1. 구문 분석: 컴파일러가 a > b 표현식 분석
  2. 중간 코드: 중간 코드로 번역
    t1 = 5
    t2 = 3
    t3 = t1 > t2
  1. 기계어: 중간 코드는 기계어로 번역
    MOV eax, 5 ; eax에 5를 로드
    MOV ebx, 3 ; ebx에 3을 로드
    CMP eax, ebx ; eax와 ebx를 비교
    SETG al ; 크면 al 설정
  1. 실행: CPU는 이 명령어들을 실행 및 비교 수행. 결과를 지정된 레지스터나 메모리에 저장

이 단계를 통해 컴퓨터가 관계 연산자를 처리하는 복잡성과 효율성을 이해할 수 있으며, 하드웨어 수준에서 정확하고 효율적인 비교를 보장한다.

그럼 <, <=, >, >= 중 어떤 연산자가 가장 효율적일까?

모든 연산자가 플래그 레지스터를 검사하는 방식이 다르지만, 실질적인 연산 횟수나 복잡도 측면에서 큰 차이는 없다. 요즘 CPU의 성능 최적화 기술 덕분에 이러한 미세한 차이는 성능에 거의 영향을 미치지 않는다.

결론적으로 성능 차이는 매우 미미하므로 코드의 논리적 흐름과 가독성을 유지하는 것이 더 중요하다. 필요한 연산자를 사용하여 코드의 의도를 명확히 표현하는 것이 좋다.

각 연산자의 플래그 검사

  1. < 연산자 (JL - Jump if Less)
    • 플래그 검사: SF != OF
  2. <= 연산자 (JLE - Jump if Less or Equal)
    • 플래그 검사: ZF == 1 or SF != OF
  3. > 연산자 (JG - Jump if Greater)
    • 플래그 검사: ZF == 0 and SF == OF
  4. >= 연산자 (JGE - Jump if Greater or Equal)
    • 플래그 검사: SF == OF

*참고: http://unixwiz.net/techtips/x86-jumps.html

결론

어떤 연산자를 써도 성능 차이가 미미하지만 플래그 검사 측면에서 가장 적은 비교를 수행하는 연산자는 < 연산자 (JL)와 >= 연산자 (JGE)다.

profile
just do it

0개의 댓글