[C/C++] 2. 정수형 자료형과 2의 보수
앞서 변수와 메모리의 기초 단위(Bit, Byte)를 알아보았다. 이번 글에서는 정수형 자료형이 데이터를 표현하는 두 가지 방식인 부호 있는 정수(Signed) 와 부호 없는 정수(Unsigned), 그리고 컴퓨터가 음수를 처리하는 원리인 2의 보수(2's Complement)를 정리한다. 1바이트(8비트)는 총 $2^8 = 256$가지의 상태를 표현할 수 있다. 이 256가지의 상태를 어떻게 해석하느냐에 따라 표현 범위가 완전히 달라진다. - Unsigned (부호 없음): 256가지 상태를 오직 0과 양수에만 배정한다. (범위: $0 \sim 255$) - Signed (부호 있음): 256가지 상태를 음수와 양수에 절반씩 나누어 배정한다. (범위: $-128 \sim 127$) 자료형에서는 8개의 비트 중 가장 왼쪽에 위치한 비트를 MSB(Most Significant Bit, 최상위 비트)라고 부른다. - MSB가 인 경우: 양수로 해석한다. - MSB가 인 경우: 음수로 해석한다. 즉, MSB는 해당 숫자가 양수인지 음수인지 판별하는 '부호 비트'의 역할을 수행한다. 컴퓨터는 뺄셈을 전용으로 처리하는 회로를 따로 두지 않고, "음수의 덧셈"으로 처리하여 연산 속도를 최적화한다. 그렇다면 컴퓨터는 음수를 어떻게 정의할까? > 음수의 정의: 어떤 양수 $A$와 더했을 때 $0$이 되는 수 8비트 공간에서 ()과 더해서 을 만들 수 있는 비트 패턴은 모든 비트가 로 채워진 이다. Plaintext 따라서 은 Signed 기준 을 의미한다. 어떤 양수에 대응하는 음수 비트를 찾는 방법은 매우 간단하다. 1. 모든 비트를 반전시킨다. ( -> , -> ) 2. 결과에 을 더한다. 동일한 메모리 공간에 동일한 비트()가 저장되어 있어도, 개발자가 선언한 자료형에 따라 컴퓨터가 해석하는 값이 달라진다. - 로 해석 시: - 로 해석 시: 비트 데이터 자체는 동일하지만, 자료형이라는 '설명서'에 따라 전혀 다른 값으로 읽히는 것이다. 비트 수가 늘어날수록 표현 가능한 수의 범위는 기하급수적으로 증가한다. | 자료형 | 크기 | Unsigned 표현 범위 | Signed 표현 범위 | | --------- | ------------- | ----------------------------- | ------------------------------------------------- | | char | 1 바이트 (8 비트) | $0 \sim 255$ | $-128 \sim 127$ | | short | 2 바이트 (16 비트) | $0 \sim 65,535$ | $-32,768 \sim 32,767$ | | int | 4 바이트 (32 비트) | $0 \sim \text{약 } 42\text{억}$ | $\text{약 } -21\text{억} \sim \text{약 } 21\text{억}$ | > 💡 게임 개발 시 자료형 선택이 중요한 이유 > >고전 게임에서 캐릭터 스탯이나 아이템 최대 수량이 255로 제한되어 있던 이유는 1바이트() 자료형을 사용했기 때문이다. > >만약 현대 게임에서 몬스터의 HP를 로 설정해 두었는데, 파워 인플레이션으로 인해 HP가 21억을 넘어서는 순간 오버플로우가 발생하여 HP가 음수()로 변해버리는 치명적인 버그가 발생할 수 있다. 따라서 담고자 하는 데이터의 최댓값을 고려하여 적절한 크기(, 등)와 부호 키워드()를 선택하는 것이 중요하다. --- 동일한 이진수 데이터라도 어떤 자료형으로 바라보느냐에 따라 전혀 다른 의미를 가진다. 자료형의 한계 범위를 명확히 인지하고 코드를 작성하는 습관을 들이도록 하자.
[C/C++] 1. 변수와 자료형
프로그래밍을 처음 시작할 때 가장 먼저 마주하게 되는 개념인 자료형(Data Type)과 변수(Variable), 그리고 그 밑바탕이 되는 메모리 단위를 정리한다. 코드를 작성할 때 사용하는 와 같은 구문은 각자 명확한 역할을 수행한다. - 자료형(): 변수가 어떤 형태의 데이터를 저장할지, 메모리에서 어느 정도의 크기를 차지할지를 결정하는 설명서역할을 한다. - 변수(): 데이터를 저장하는 실제 메모리 공간에 붙이는 이름표다. 개발자가 직접 이름을 정할 수 있다. 자료형은 크게 정수형과 실수형으로 나뉘며, 어떤 자료형을 선택하느냐에 따라 컴퓨터가 데이터를 처리하는 방식과 메모리 점유 크기가 결정된다. 프로그래밍에서 메모리 크기를 말할 때 사용하는 단위인 '바이트(Byte)'에 대한 개념을 잡는 것이 중요하다. - 비트(Bit): 컴퓨터가 정보를 처리하는 최소 단위로, 또는 의 두 가지 상태만 가질 수 있다. - 바이트(Byte): 8개의 비트가 모여 1바이트가 된다. - 데이터 크기: 대표적인 정수형인 형은 4바이트(32비트) 크기를 가지며, 이를 통해 정수 데이터를 효율적으로 저장한다. 자료형은 단순히 데이터를 담는 그릇이 아니라 데이터를 표현하는 방식과 범위를 정의한다. 특히 1바이트 크기의 자료형을 보면 이해가 쉽다. - 경우의 수: 1바이트(8비트)는 $2^8 = 256$가지의 상태를 표현할 수 있다. - unsigned 키워드: 는 말 그대로 부호(sign)가 없다는 뜻이다. 음수를 표현하지 않는 대신, 모든 비트를 양수를 나타내는 데 사용하여 표현 범위를 2배 넓혀준다 (0~255). 변수가 수용할 수 있는 범위를 넘어서는 데이터를 넣으면 오버플로우(Overflow) 라는 현상이 발생하여 예상치 못한 결과가 나온다. > 💡 쉽게 이해하기 > 손으로 누르는 3자리 수동 카운터(클리커) 가 999에서 버튼을 한 번 더 누르면 000으로 리셋되는 것과 같다. 1바이트(8비트) 공간에 256을 넣으려 하면, 8비트를 넘어선 상위 비트 정보는 사라지고 남은 비트 값()만 남게 된다. 컴퓨터 언어는 명시한 자료형의 규칙을 엄격하게 따르기 때문에, 개발자는 변수를 선언할 때 저장할 데이터의 범위를 항상 고려해야 한다. --- , 같은 자료형은 컴퓨터가 데이터를 체계적으로 관리하기 위한 약속이다. 코드 한 줄을 짤 때마다 이 변수는 몇 바이트이고, 어떤 데이터를 담을 것인가를 고민하는 습관을 들인다면, 코딩 실력은 한층 더 견고해질 것이다.
리액트 네이티브 CLI 셋업
React Native Android 빌드에 사용할 JDK는 Java 17 버전이다. - 자바 설치 링크 - 설치 파일 실행 후 단계 따라 설치 - 설치 후 환경변수 설정 - 변수에 Java 17 설치 경로 추가 (예: ) - 시스템 변수 에 추가 - 커맨드 라인에서 버전 확인: - Android Studio 설치 - Android Studio 설치 링크 - Android SDK, 가상 디바이스(AVD) 생성 및 하드웨어 가속 설정 - Android Studio 실행 후 More Actions 클릭 - Virtual Device Manager 선택 - '+' 클릭 후 Pixel 아무거나 선택하고 Next - API 36+ Google Play Intel x86_64 Atom System Image 선택 후 Finish - 환경 변수 설정 - ANDROID_HOME 변수에 SDK 경로 지정 (%USERPROFILE%\AppData\Local\Android\Sdk) - Path 변수에 아래 폴더들 추가: - %ANDROID_HOME%\emulator - %ANDROID_HOME%\tools - %ANDROID_HOME%\tools\bin - %ANDROID_HOME%\platform-tools
2026년 3월 10일
리액트 네이티브 CLI Nativewind 적용
prettier-plugin-tailwindcss@^0.5.11 설치 중 prettier@^3.0 에러가 발생할 경우
2026년 3월 10일
npm error signal SIGSEGV 에러가 발생했다.
위 코드를 보면 로그가 발생한 후에 3번 로그인 로그가 발생하지 않았다. 그래서 정말 bcrypt 라이브러리에서 hash를 하는 과정에서 문제였는지 주석처리를 하고 테스트를 해보았다. 이렇게 임의의 문자열을 넣고 회원가입 테스트를 해봤고, db에 정상적으로 잘 입력되었다. 사실 로컬환경에서는 도커를 사용하지 않고, 배포환경에서는 CI/CD 자동화 파이프라인이 구축되어있어서 환경이 많이 다르긴하다. 하지만 비밀번호 해싱해주는 라이브러리가 문제를 일으킬줄은 상상도 못했다. 찾아보니 bcrypt 라이브러리는 C++로 만들어진 C++ 네이티브 모듈이었다. 1. 첫번째 이유는 성능 이다. 비밀번호 암호화는 많은 연산을 필요로 한다. 무차별 대입 공격(Brute-force attack)을 어렵게 만들기 위해 의도적으로 연산량이 많도록 설계된 라이브러리이다. 그래서 C++와 같은 고급언어 중 저수준 언어로 만들어서 성능을 최적화한 것이다. 2. 두번째 이유는 보안 이다. 핵심 로직을 C++로 구현하면 일부 보안 취약점에 더 강할 수 있다. 물론 bcrypt의 핵심 부분이 C++로 되어 있다고 하여서 무조건 "C++가 JS보다 더 안전하다"는 일반론 적인 이야기는 아니다. 오히려 C++는 메모리에 직접 접근하기 때문에 메모리 버그(예: 버퍼 오버플로우)가 생기기 쉬워서 문제가 생길 수 있다. (설명이 길어질 수 있으므로 나중에 따로 정리하겠다.) 문제는 C++로 작성된 부분을 도커 컨테이너에 맞게 해서 사용한다는 점이다. - 로컬환경에서는 이미 C++ 컴파일에 필요한 같은 도구들이 이미 준비되어 있거나 Node.js가 잘 처리해줘서 할 때 bcrypt의 C++부분이 문제없이 잘 빌드가 되었기 때문에 SIGSEGV에러가 발생하지 않았을 가능성이 있다. - 도커 이미지 안에는 개발 또는 빌드에 필요한 모든 도구가 기본적으로 다 제공되진 않는다. 이나 같은 C++ 컴파일 관련 도구들이 없으면 할 때 컴파일 도구를 찾지 못해서 에러를 내거나, 빌드에 관련 도구들이 누락이 되버린다. 그래서 나중에 컴파일 도구가 필요한 라이브러리를 사용하게 되면 지금과 같은 상황이 되버린다. --- 이렇게 가 떴던 원인은 정확히 "bcrypt 라이브러리의 C++ 네이티브 모듈이 도커 컨테이너 환경에서 제대로 빌드되지 않았기 때문" 이었다. 처음에 에 이랑 를 추가해서 컨테이너 안에 C++ 컴파일 도구를 넣어주는 시도도 해봤다. 하지만 이 악독한 는 결국 나를 떠나지 않았다.. 결국, 고민 끝에 내린 특단의 조치는 바로 * 대신 를 사용하는 것이었다. 는 와 동일한 암호화 알고리즘을 사용하지만, *순수 JavaScript로만 구현된 라이브러리* 다. C++ 네이티브 모듈 부분이 전혀 없기 때문에, 컴파일 문제나 도커 환경에서의 호환성 문제로부터 완전히 자유로울 수 있다는 것이 핵심이다. 는 C++이라 빠르고, 는 JS라 느려서 많이 답답할 것이라 생각할 것이다. 하지만 Node.js 환경에서 가 잘 작동하는 데는 중요한 이유가 있다. JavaScript는 기본적으로 싱글 스레드 기반이라 CPU 집약적인 동기 작업은 메인 스레드를 블로킹해서 서버를 먹통으로 만들 수 있다. 하지만 의 함수는 *비동기적으로 동작* 하도록 설계되어 있다. Node.js는 라는 라이브러리를 통해 *Worker Pool (스레드 풀)* 기능을 제공하는데, 의 CPU를 많이 쓰는 해싱 연산은 메인 JavaScript 스레드가 아닌, 이 *백그라운드 스레드에서 처리* 된다. 즉, 해싱 작업이 진행되는 동안에도 메인 스레드는 다른 요청들을 계속 처리할 수 있다는 것이다. 결국, 는 C++ 보다 단일 해싱 작업 속도는 미세하게 느릴 수 있지만, Node.js의 *비동기 처리 덕분에 메인 스레드를 블로킹하지 않아서 전체 서버의 처리량(Throughput)에는 큰 영향을 주지 않는다.* 오히려 네이티브 모듈 컴파일 문제 없이 *어느 환경에서나 쉽고 안정적으로 배포될 수 있다는 압도적인 장점* 이 있는 것이다. 내가 겪었던 문제가 바로 가 왜 필요한지를 몸소 보여준 예시라고 할 수 있겠다. 1. *기존 제거 및 설치* 1. *코드 수정 ( 및 비밀번호 검증 로직)* 를 import 했던 부분을 로 바꾸고, 비동기 함수를 사용하도록 코드를 변경했다. 2. * 원복 (이제 필요 없음)* 이나 설치 라인은 에게는 필요 없으니 에서 깨끗하게 지웠다. 3. *다시 도커 커밋후 배포* 결과는? *성공이었다. 드디어 는 더 이상 뜨지 않았고, 회원가입과 로그인이 정상적으로 작동했다.* --- 이번 삽질 대잔치를 통해 여러 가지를 느꼈다. - *네이티브 모듈이 포함된 라이브러리를 사용할 때는 도커 컨테이너의 OS 환경과 빌드 도구 유무를 꼼꼼히 확인해야 한다. - 와 같은 메모리 관련 에러는 대부분 저수준(C/C++, 하드웨어) 문제와 연결되어 있음을 인지하고 접근해야 한다. 처음에는 Prisma 문제인 줄 알고 삽질 오지게 했다... - 문제가 발생했을 때 **로그를 꼼꼼히 작성하고 분석하는 습관이 얼마나 중요한지 다시 한번 실감했다. 로그가 찍히기 전에 에러가 났다는 걸 아는 것이 문제 해결의 시작점이었다. - 순수 JavaScript 대안 라이브러리(예: )의 중요성. 네이티브 모듈의 성능 이점을 잃는 대신, 배포의 복잡성과 에러 가능성을 크게 줄일 수 있다는 트레이드오프를 명확히 이해하게 되었다. 하지만, bcryptjs로 교체한 것은, 임시방편일 뿐.. 기말고사만 끝나면, 다시 한번 bcrypt를 시도해보려고 한다.
2025년 11월 21일