
논리 게이트: AND, OR, NOT, XOR
컴퓨터는 어떻게 판단을 할까? 복잡한 AI도 결국 이 4가지 게이트의 조합일 뿐입니다.

컴퓨터는 어떻게 판단을 할까? 복잡한 AI도 결국 이 4가지 게이트의 조합일 뿐입니다.
내 서버는 왜 걸핏하면 뻗을까? OS가 한정된 메모리를 쪼개 쓰는 처절한 사투. 단편화(Fragmentation)와의 전쟁.

미로를 탈출하는 두 가지 방법. 넓게 퍼져나갈 것인가(BFS), 한 우물만 팔 것인가(DFS). 최단 경로는 누가 찾을까?

이름부터 빠릅니다. 피벗(Pivot)을 기준으로 나누고 또 나누는 분할 정복 알고리즘. 왜 최악엔 느린데도 가장 많이 쓰일까요?

매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

처음 프로그래밍을 배울 때, if (a && b) 같은 코드를 짜면서 "컴퓨터가 이걸 어떻게 이해하지?"라고 궁금했습니다. 소프트웨어 세계에서는 당연히 쓰는 AND, OR 같은 논리 연산자가, 하드웨어에서는 정말 물리적인 회로로 존재한다는 게 신기했어요.
그냥 "CPU가 알아서 판단해주겠지"라고 생각했는데, 알고 보니 그 "판단"의 정체는 트랜지스터를 조합해서 만든 논리 게이트였습니다. 즉, 전기 신호가 0인지 1인지에 따라 회로가 열리고 닫히면서 "참/거짓"이 결정되는 구조였던 거죠.
나는 이 사실을 받아들이는 데 좀 시간이 걸렸습니다. "컴퓨터가 생각한다"는 표현은 좀 과장이고, 사실은 아주 빠르게 전기 스위치를 켜고 끄면서 규칙대로 전기를 흘려보내는 것에 불과하다는 걸 이해했습니다.
논리 게이트(Logic Gate)는 입력된 전기 신호(0 또는 1)를 받아서, 정해진 규칙에 따라 출력(0 또는 1)을 내보내는 회로입니다. 우리가 코드에서 쓰는 조건문의 물리적 실체라고 받아들였습니다.
처음엔 "그냥 전기 스위치 아니야?"라고 생각했는데, 이 게이트들이 조합되면 덧셈, 뺄셈, 비교, 저장 같은 모든 연산이 가능하다는 걸 알고 충격받았습니다.
규칙: 입력 두 개가 모두 1이어야 출력이 1입니다.
진리표(Truth Table):
| A | B | 출력 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
비유: 금고 문을 열려면 두 개의 열쇠를 동시에 꽂아야 합니다. 하나라도 없으면 열리지 않아요.
코드로 보면:
// JavaScript
if (user.isLogin && user.hasTicket) {
console.log("입장 가능");
}
// 비트 연산자로 직접 표현
let a = 1, b = 1;
console.log(a & b); // 1 (AND 연산)
a = 1, b = 0;
console.log(a & b); // 0
트랜지스터로는 어떻게?: 트랜지스터 두 개를 직렬로 연결합니다. 마치 수도관에 수도꼭지 두 개를 일렬로 설치하는 거죠. 물이 나오려면(1) 두 꼭지를 모두 열어야 합니다.
규칙: 입력 중 하나라도 1이면 출력이 1입니다.
진리표:
| A | B | 출력 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
비유: 현관 도어락. 비밀번호를 누르거나 카드키를 대거나 둘 중 하나만 해도 문이 열립니다.
코드로 보면:
if (isWeekend || isHoliday) {
console.log("쉬는 날");
}
let a = 1, b = 0;
console.log(a | b); // 1 (OR 연산)
a = 0, b = 0;
console.log(a | b); // 0
트랜지스터로는 어떻게?: 트랜지스터 두 개를 병렬로 연결합니다. 수도관이 두 갈래로 나뉘어 있고, 어느 한쪽 꼭지만 열어도 물이 나옵니다.
규칙: 입력이 0이면 1, 입력이 1이면 0으로 바꿉니다. 청개구리 게이트죠.
진리표:
| A | 출력 |
|---|---|
| 0 | 1 |
| 1 | 0 |
비유: 비상 정지 버튼. 평소엔 전기가 흐르다가(1), 버튼을 누르면(1 입력) 전기가 끊깁니다(0 출력).
코드로 보면:
if (!isPaid) {
console.log("결제하지 않았음");
}
let a = 1;
console.log(~a); // -2 (비트 반전, 정확히는 보수 연산)
console.log(!a); // false (논리 반전)
규칙: 두 입력이 서로 다를 때만 1을 출력합니다. 같으면 0입니다.
진리표:
| A | B | 출력 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
비유: 두 사람이 각자 동전을 던졌을 때, 하나는 앞면이고 하나는 뒷면일 때만 상금을 줍니다. 둘 다 앞면이거나 둘 다 뒷면이면 상금 없음.
코드로 보면:
# Python
a, b = 1, 0
print(a ^ b) # 1 (XOR 연산)
a, b = 1, 1
print(a ^ b) # 0
# 암호화에 자주 쓰임
message = 0b1010
key = 0b1100
encrypted = message ^ key # 0110
decrypted = encrypted ^ key # 1010 (원본 복구)
활용: XOR는 암호화, 패리티 체크, 데이터 무결성 검사 등에 엄청 자주 쓰입니다. 같은 키로 두 번 XOR하면 원본이 복구되는 성질 덕분이죠.
NAND = NOT + AND. AND 결과를 반전시킵니다. NOR = NOT + OR. OR 결과를 반전시킵니다.
NAND 진리표:
| A | B | 출력 |
|---|---|---|
| 0 | 0 | 1 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
NOR 진리표:
| A | B | 출력 |
|---|---|---|
| 0 | 0 | 1 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 0 |
충격적인 사실: NAND 게이트 하나만 있으면 나머지 모든 게이트(AND, OR, NOT, XOR)를 다 만들 수 있습니다. NOR도 마찬가지예요.
그래서 실제 CPU 설계할 때 NAND 게이트를 기본 단위로 많이 씁니다. 제조 비용이 싸고 효율적이거든요. 우리가 쓰는 USB 메모리나 SSD의 NAND Flash도 이 구조를 기본 단위로 쓰기 때문에 "NAND"라는 이름이 붙었습니다.
논리 게이트를 처음 배울 때 "이게 대체 어디다 쓰이나?"라고 생각했는데, 이걸 조합하면 산술 연산을 할 수 있다는 걸 알고 감동받았습니다.
두 개의 1비트 숫자를 더하는 회로입니다.
예시: 1 + 1 = 10 (이진수)
회로 구성:
// 반가산기를 코드로 표현
function halfAdder(a, b) {
const sum = a ^ b; // XOR
const carry = a & b; // AND
return { sum, carry };
}
console.log(halfAdder(1, 1)); // { sum: 0, carry: 1 }
console.log(halfAdder(1, 0)); // { sum: 1, carry: 0 }
반가산기는 이전 자리의 올림(Carry)을 처리하지 못합니다. 전가산기는 세 개의 입력(A, B, Carry-in)을 받아서 합과 올림을 출력합니다.
회로 구성:
function fullAdder(a, b, carryIn) {
const firstSum = a ^ b;
const firstCarry = a & b;
const sum = firstSum ^ carryIn;
const secondCarry = firstSum & carryIn;
const carryOut = firstCarry | secondCarry;
return { sum, carryOut };
}
console.log(fullAdder(1, 1, 1)); // { sum: 1, carryOut: 1 } (1+1+1=11)
와닿은 점: CPU가 숫자를 더하는 것도 결국 이 전가산기를 여러 개 연결한 거라는 사실. 32비트 덧셈이면 전가산기 32개를 일렬로 연결합니다. 이게 바로 ALU(Arithmetic Logic Unit)의 핵심입니다.
논리 게이트를 다루다 보면 회로를 단순화하고 싶을 때가 있습니다. 여기서 불 대수가 쓰입니다.
드 모르간의 법칙:
NOT(A AND B) = NOT(A) OR NOT(B)NOT(A OR B) = NOT(A) AND NOT(B)코드로 확인:
let a = true, b = false;
// 법칙 1
console.log(!(a && b) === (!a || !b)); // true
// 법칙 2
console.log(!(a || b) === (!a && !b)); // true
실용적인 활용: 코드 리팩터링할 때 조건문을 단순화하는 데 씁니다.
// 원래 코드
if (!(isLoggedIn && hasPermission)) {
console.log("접근 불가");
}
// 드 모르간 적용
if (!isLoggedIn || !hasPermission) {
console.log("접근 불가");
}
두 번째 코드가 훨씬 읽기 쉽죠. 나는 이 법칙을 알고 나서 조건문 짤 때 자연스럽게 적용하게 됐습니다.
현대 CPU는 트랜지스터를 수십억 개 집적합니다. 예를 들어 Apple M1 칩은 160억 개의 트랜지스터가 들어 있습니다.
왜 이렇게 많이 필요한가?비유: 레고 블록 하나하나가 트랜지스터라고 생각하면, CPU는 수십억 개의 레고를 조립해서 만든 거대한 성입니다. 하나의 성벽(ALU)을 쌓는 데도 수백만 개의 블록이 들어가는 거죠.
우리가 짠 코드가 실행되려면 결국 논리 게이트를 통과해야 합니다.
if (x > 5 && y < 10) {
console.log("조건 만족");
}
하드웨어 관점에서 해석:
x > 5: CPU의 비교 회로(Comparator)가 작동합니다. 내부적으로는 뺄셈(x - 5)을 수행하고, 결과가 양수인지 확인하는 논리 게이트 조합이 동작합니다.y < 10: 마찬가지로 비교 회로가 작동합니다.&&: 두 비교 결과를 AND 게이트에 입력합니다.결국 이거였다: 우리가 짠 IF 문도 결국 CPU 어딘가에 있는 AND 게이트, OR 게이트를 지나가는 전기 신호로 변환된다는 사실. 이걸 받아들이니까, 코딩할 때 "내가 지금 하드웨어의 스위치를 제어하고 있구나"라는 느낌이 들었습니다.
JavaScript나 Python에서도 비트 연산자를 쓰면 논리 게이트를 직접 흉내낼 수 있습니다.
# Python
a = 0b1100 # 12 (이진수)
b = 0b1010 # 10
print(bin(a & b)) # 0b1000 (AND)
print(bin(a | b)) # 0b1110 (OR)
print(bin(a ^ b)) # 0b0110 (XOR)
print(bin(~a)) # -0b1101 (NOT, 보수 연산)
print(bin(a << 1)) # 0b11000 (왼쪽 시프트, *2)
print(bin(a >> 1)) # 0b110 (오른쪽 시프트, /2)
실용 사례:
if (userPermissions & PERMISSION_WRITE)flags |= FLAG_ACTIVEflags &= ~FLAG_ACTIVEif (n & 1) (마지막 비트가 1이면 홀수)나는 비트 연산자를 배우고 나서 "아, 이게 바로 하드웨어 레벨의 제어구나"라고 이해했습니다.
[반가산기 회로]
A ----\
XOR ---- Sum
B ----/
A ----\
AND ---- Carry
B ----/
[전가산기 회로]
A -----\
XOR -----\
B -----/ \
XOR ---- Sum
Cin --------------/
(중간 캐리 계산 생략)
|
OR ----- Carry Out
실제 회로는 훨씬 복잡하지만, 개념적으로는 이런 식으로 XOR, AND, OR 게이트를 조합해서 만듭니다.
CPU 안에는 ALU라는 부품이 있습니다. 이게 바로 산술 연산(덧셈, 뺄셈, 곱셈 등)과 논리 연산(AND, OR, NOT 등)을 처리하는 핵심 부품이에요.
ALU의 구성:
코드 한 줄이 ALU를 통과하는 과정:
let result = (a + b) & 0xFF;
a + b: ALU의 가산기 회로가 작동 (전가산기 체인)& 0xFF: ALU의 AND 게이트 회로가 작동나는 이 흐름을 이해하고 나서, 코드 최적화할 때 "이 연산이 ALU에서 몇 사이클 걸릴까?"를 고민하게 됐습니다.
논리 게이트는 컴퓨터의 가장 기초적인 판단 단위입니다. 우리가 짠 코드의 모든 조건문, 모든 연산은 결국 이 게이트들을 통과하는 전기 신호로 변환됩니다.
이 게이트들을 조합하면:
결국 이거였다: 컴퓨터의 "생각"은 생각이 아니라, 트랜지스터로 만든 논리 게이트를 통과하는 전기 신호의 흐름일 뿐이라는 사실. 이걸 받아들이니까, 프로그래밍이 훨씬 더 구체적이고 물리적으로 느껴졌습니다.