Aspect-Oriented Programming의 이해와 적용

객체 지향 프로그래밍(Object-Oriented Programming, a.k.a. OOP)은 코드를 객체라는 독립적인 모듈로 묶어 관리한다. 각 객체는 관심사의 분리 원칙에 따라 만들어져야 하는데, 이는 각 객체가 자신의 특정 책임에 집중해야 한다는 의미다. 그러나 실제 서비스를 개발하다보면 여러 객체나 모듈에 걸쳐 공통적으로 나타나는 기능들을 종종 만나게 된다. 이렇게 여러 모듈에 걸쳐 있는 기능들을 횡단 관심사 (Cross-cutting Concerns)라고 부른다. 횡단 관심사가 가지는 문제점 Scattering 이러한 횡단 관심사들은 OOP만으로 다룰 경우 두 가지 주요 문제를 만든다. 첫 번째 문제는 Scattering 이라고 부른다. Scattering은 특정 기능의 코드가 복사 붙여넣기를 통해 여러 곳에 흩뿌려지는 현상을 말한다. 예를 들어, 모든 함수에 사용자 권한 확인 로직과 로깅 코드를 추가한다고 해보자. 동일한 로깅 및 권한 확인 코드가 각 메서드 내에 반복적으로 나타날 것이다. Tangling 다른 문제는 Tangling 이다. 직역하면 얽힘이라고 할 수 있는데, 이는 비즈니스 로직과 횡단 관심사의 코드가 하나의 모듈이나 함수 안에서 뒤섞여 복잡해지는 현상을 말한다. get_book_info 함수의 주된 목적은 원하는 책의 정보를 찾는 것이다. 하지만 로깅, 권한 확인, 성능 측정 등 다양한 관심사가 그 안에 뒤얽혀 있다. 이 때문에 이 함수의 가독성은 떨어지고 핵심 비즈니스 로직을 파악하기 어려워진다. 이는 마치 지저분한 책상과 같다. 익숙해지면 무엇이 어딨는지 한 번에 알 수 있지만, 익숙하지 않은 사람은 무엇이 어디있는지 찾기 힘들어진다. Aspect-Oriented Programming의 등장 관점 지향 프로그래밍(Aspect-Oriented Programming, a.k.a. AOP)은 이런 문제를 비즈니스 로직 과 횡단 관심사 를 분리하여 해결하려 한다. AOP는 90년대 후반 Xerox PARC...

USB를 이용한 전원 공급 (2) - USB PD

이미지
USB 포트는 원래 데이터 전송을 위한 인터페이스였지만, 시간이 지나면서 전력 공급 수단으로도 활용되기 시작했다. 이를 지원하기 위해 USB BC 가 도입되었고, 기존보다 높은 전류를 제공할 수 있도록 개선되었다. 하지만 USB BC 1.2는 5 V 전압 고정이라는 한계를 갖고 있어, 노트북 같은 고출력 기기를 충전하기에는 부족했다. 이런 제약 조건을 해결하기 위해 제조사마다 별도의 급속 충전 방식을 만들기 시작했고, 이로 인해 특정 브랜드의 디바이스는 해당 브랜드의 충전기에서만 고속 충전이 가능한 호환성 문제도 발생하였다. 이러한 문제를 해결하기 위해 USB-IF는 USB Power Delivery(a.k.a. USB PD)라는 전력 공급을 위한 새로운 표준을 만들었다. USB PD는 전압과 전류를 동적으로 협상하는 방식을 채택하여, 5 V 뿐만 아니라 9 V , 15 V , 20 V 등 다양한 전압을 지원하고 최대 240 W 까지 전력을 공급할 수 있도록 설계되었다. 이를 통해 스마트폰부터 노트북, 모니터, 이론상으로는 일부 데스크톱 기기까지 USB-C 하나로 전원 공급이 가능해졌으며, 장치 간의 호환성과 범용성이 크게 향상되었다. USB PD와 CC 라인 USB PD가 USB BC보다 더 많은 전력을 공급할 수 있었던 것은 Type-C 컨넥터, 특히 CC 핀의 존재 덕분이다. USB Type-C 커넥터는 좌우 대칭 구조를 채택하여 어느 방향으로 꽂아도 동작할 수 있도록 설계되었으며, 이 방향 감지를 담당하는 것이 바로 Configuration Channel(a.k.a. CC) 핀이다. 기본적으로 CC 핀은 케이블의 방향을 감지하고, 연결된 장치가 전력 공급자인지 소비자인지 식별하는 데 사용된다. USB PD에서는 이 CC 핀을 이용해 전력 협상을 위한 디지털 통신을 수행한다. 소스 디바이스(전력을 공급하는 기기)는 자신이 제공할 수 있는 전력 프로파일을 CC 핀을 통해 싱크 디바이스(전력을 소비하는 기기)에게 알리고, 싱크 디바이스는 그 중...

USB를 이용한 전원 공급 (1) - USB BC

이미지
USB는 데이터 전송을 목적으로 설계되었지만, 전원 공급 기능도 포함하고 있다. 이는 USB로 연결된 간단한 저전력 기기를 동작시키기 위한 것이다. 그래서 USB 2.0에서는 5V 전압과 0.5A의 전류를, USB 3.2에서는 5V 전압과 0.9A의 전류 공급이 가능하다. 하지만 이 스펙은 어디까지나 USB를 통한 데이터 통신을 하는 데 필요한 디바이스를 동작시키기 위함이지, USB를 전원 공급을 위해 이용하려는 목적은 아니었다. 따라서 저전력 기기가 아닌 외장 하드 같은 디바이스는 별도의 전원 공급을 필요로 했고, USB를 통한 전원 충전은 USB가 본래 의도했던 기능이 아닌 일종의 부작용에 가까운 일이었다. 하지만 iPod을 비롯한 많은 MP3 플레이어나 PMP 플레이어들이 이를 이용한 충전 기능을 가지고 나왔다. 어차피 데이터 통신을 위해 USB 포트가 필요하니 별도의 충전 포트를 만드는 것보다 USB 포트를 재사용하는 것이 기기를 싸고 가볍고 작게 만들 수 있었기 때문이다. 결국 브랜드마다 독자적인 USB를 통한 전원 충전 규격들이 만들어졌. 사람들은 이런 혼란스러운 상황이 해결되기를 원했고, 결국 2007년 USB-IF는 USB Battery Charging(a.k.a. BC)라는 표준을 만들어 USB 충전기를 표준의 영역으로 가지고 왔다. SDP DCP CDP 데이터 전송 가능 데이터 전송 불가 데이터 전송 가능 최대 0.5A(USB 2.0) 최대 0.9A(USB3.x) 최대 1.5A 최대 1.5A 별도 핸드셰이크 없음 D+/D- 쇼트 D+/D- 라인에 독립적으로 전압을 가해 핸드셰이크 데스크톱, 노트북 등에서 사용 USB 충전기에서 주로 사용 데스크톱, 노트북...

USB 3.x 케이블 무엇을 골라야 하나

3.1? 3.2? 이건 뭐지? Gen 1? Gen 2? 이건 뭐지? USB 케이블이 필요해 인터넷에 검색하면 다양한 케이블이 나온다. 여기서 적당한 케이블을 골라야 하는데 보통은 여기서부터 막막해진다. 3.1과 3.2의 차이는 무엇이고 3.1 Gen 2와 3.2 Gen 2는 무슨 차이가 있을까? 3.2 Gen 1은 3.1 Gen 2보다 좋은 것일까? 사람들에게 혼란을 주는 가장 큰 요인은 USB 3.x의 복잡한 명명 방식이라고 생각한다. USB 3.0, USB 3.1, USB 3.2. 이름만 보면 USB 3.1은 USB 3.0보다 발전됐고, USB 3.2는 USB 3.1보다 발전된 것으로 보인다. 하지만 USB 3.2에서 규정하는 모든 기술이 USB 3.1에서 규정하는 모든 기술보다 발전한 기술은 아니다. 그 이유는 이들 표준이 이전 버전을 포함하는 방식으로 설계됐기 때문이다. 예를 들어, USB 3.1 표준은 USB 3.0 표준 문서에서 정의된 기능과 추가된 기능을 포함하며, USB 3.2 표준은 USB 3.1 표준 문서에서 정의하는 기능과 새로운 기능을 포함하고 있다. 이렇게 포함 구조로 설계된 표준은 표준 문서라는 기술적 측면에서는 합리적인 선택이다. 하지만 이런 이름이 좋은 브랜딩 방식이라고 생각하지 않는다. 예를 들어 이 글에서도 USB 3.x 표준에 기반한 케이블은 정확히 부를 이름이 없어 그냥 USB 3.x라는 이름으로 뭉뜽그려 부르고 있다. 이와 같은 혼란을 해소하기 위해 USB4에서는 모든 표준을 USB4라는 하나의 브랜드로 묶고, 표준 문서에 Version 1과 Version 2라고 버전을 붙이는 방식을 택했다. 이는 소비자들이 버전 간 차이를 명확히 이해할 수 있도록 노력한 것으로 보인다. USB 3.0 우선 USB 3.0이 USB 2.0과 비교해 가지는 가장 큰 장점은 전송속도다. 기존에 사용하던 USB 2.0 케이블은 최대 480 Mbps. 즉, 60 MB/s의 전송 속도를 가졌다. USB 2.0이 도입되던 ...

USB 2.0 과 3.x의 컨넥터 호환성

이미지
지난 글 에서 설명했듯이 USB 2.0에서는 VCC / GND / D+ / D- 4개의 케이블이 있기 때문에 4개의 핀만 필요하다. USB 3.x 케이블을 위해 필요한 케이블은 VCC / GND 와 고속 전송을 위한 두 쌍의 레인( SSRx+ , SSRx- , SSTx+ , SSTx- 라고 한다. 이에 대한 자세한 설명은 다음 기회에 하도록 하겠다.) 그리고 혹시 차폐에 쌓여있을 수 있는 노이즈를 접지로 보내 안전하게 제거하기 위한 GND_DRAIN 케이블까지 총 7개의 케이블이 사용된다. 이 중 VCC 와 GND 는 USB 2.0에서 사용하는 선과 공유하기 때문에 새로운 5개의 선이 더 필요하다. 이미지 출처: Wikipedia 이미지 출처: Wikipedia 이 5개의 선을 핀에 연결하기 위해 USB 3.0 표준은 새로운 모양의 Type B 컨넥터를 도입했다. 기존 Type B 컨넥터는 4개의 핀만을 가지고 있고 확장할 수 없는 구조로 돼있기 때문이다. 따라서 Type B 컨넥터의 경우에는 컨넥터 모양만으로도 USB 2.0 케이블인지 USB 3.0 케이블인지 쉽게 구분할 수 있다. 하지만 Type A 컨넥터나 Type C 컨넥터는 상황이 다르다. 상하 대칭으로 24개의 핀을 가져 최대 12개의 선을 연결할 수 있는 Type C 컨넥터는 컨넥터 모양 만으로 USB 2.0 케이블인지 USB 3.x 케이블인지 구분할 수 없고, 케이블에 SuperSpeed 로고가 있는지 확인해야 한다. 그렇지 않으면 다음과 같이 Type C - Type C 케이블이지만 최대 전송 속도가 480 Mbps인 케이블을 만나게 된다. USB 2.0 Type C 케이블도 존재한다. Type A 컨넥터는 상황이 좀 재밌다. Type A 컨넥터도 원래는 4개의 핀만을 지원하도록 설계됐다. 하지만 Type B와는 다르게 Type A 컨넥터는 너무 많이 사용됐다. 따라서 USB 3.x를 위해 새로운 모양의 컨넥터를...

USB 2.0 케이블의 내부 구조

이미지
아이폰도 USB-C를 사용하면서 온 세상이 USB 로 통일됐지만 실제로는 너무 다양한 USB가 존재한다. 기본 형태인 USB-A나 최근 많이 사용되는 USB-C 뿐 아니라, 보통 5핀이라고 불리는 micro-B를 포함한 다양한 USB-B 컨넥터들이 존재한다. 그래도 컨넥터는 모양이 다르기 때문에 쉽게 구분할 수 있는데 케이블은 답이 없다. 겉으로는 똑같아 보이는 케이블이라도 어떤 케이블은 데이터 통신이 안 되고 어떤 케이블은 데이터 통신이 가능하다. 이런 차이는 케이블 내부 구성에 따라 발생한다. 이번 글에서는 USB 2.0 케이블의 내부를 통해 USB 케이블에 대해 자세히 알아보겠다. Micro-B 케이블의 편조 차폐와 호일 차폐 위 사진은 집에서 돌아다니던 A - Micro-B USB 2.0 케이블의 피복을 벗겨낸 것이다. 절연체 아래로 금속 선이 있는 것을 알 수 있다. 이 선들은 금속 선이지만 전선은 아니다. 이 선은 전자기 차폐를 목적으로 들어간 금속 선이다. 실제 전선은 이 금속 선을 벗겨야 나온다. 이번에 자른 케이블에는 두 종류의 차폐가 사용됐다. 하나는 얇은 금속 호일이고, 다른 하나는 얇은 도체의 가닥으로 이루어져 있다. 전자는 보통 호일 차폐(Foil Shielding)라고 부르고 후자는 편조 차폐(Braided Shielding)라고 부른다. 이 둘은 다 외부 전자기장으로부터 전선을 보호하기 위해 사용되지만, 특성이 약간 다르다. 보통 편조 차폐가 저주파수 전자기파를 차단하는 것에 효과적이고, 호일 차폐가 고주파수 전자기파를 차단하는 데 효과적이다. USB 3.0의 고속 전송 케이블은 이 두 차폐를 사용하는 것이 필수적이고, 그 외의 경우에는 필수는 아니고 권장 사항이다. 하지만 어지간한 싸구려 케이블을 쓰지 않는 한 요즘은 USB 2.0 케이블에도 이 두 가지를 같이 사용한다. 차폐 선이 쉴드와 연결되지 않았다 하지만 고속 전송을 지원하는 케이블이 ...

Linux의 clear와 Mac의 clear는 다르다

최근 CSI Sequence에 대한 글을 열심히 쓰고 있지만, 우리가 직접 CSI Sequence를 직접 사용할 일은 거의 없다. 하지만 알게 모르게 사용하는 CSI sequence가 있다. 바로 화면을 지우는 데 사용되는 clear 라는 명령어다. clear 는 기본적으로 두 가지 종류의 CSI sequence를 사용한다. 하나는 CUrsor Position(a.k.a CUP; CSI H )다. 이것을 이용해 커서를 화면의 시작으로 돌린다. clear 를 하고 나면, 최상단 왼쪽으로 커서가 움직이는 것은 CUP 덕분이다. 두 번째 CSI sequence는 Erase in Display(a.k.a. ED; CSI 2 J )다. 이를 이용해 화면 전체를 지운다. 여기까지는 Linux와 Mac이 공유하는 동작이다. 하지만 이후 동작에서 Linux의 clear 와 Mac의 clear 는 다르게 동작한다. 간단하게 말하면 Linux의 clear 는 스크롤 버퍼를 지우고, Mac의 clear 는 지우지 않는다. 이는 Linux의 clear는 앞의 두 시퀀스에 이어 CSI 3 J 를 출력하기 때문이다. CSI 3 J 는 xterm 이 도입한 Escape Sequence의 확장으로 스크롤 버퍼에 저장된 라인을 지우는 시퀀스다. 2011년 레드 햇 진영에서 E3라고 이름붙인 뒤 로 터미널 에뮬레이터에서는 E3 확장이라고 부르기도 한다. Mac의 clear 는 이 시퀀스를 출력하지 않기 때문에 스크롤 버퍼를 남겨둔다. 이런 차이가 발생한 것은 생각보다 오래된 일은 아니다. 원래 대부분의 터미널 에뮬레이터들은 E3 확장을 비롯한 xterm 확장들을 이해하지 못했다. 당연히 이 시절에는 Linux에서 사용되던 clear 커맨드도 E3를 사용하지 않았기 때문에 스크롤 버퍼는 지우지 않았다. 하지만 시간이 지나며 점점 xterm 확장은 다른 터미널 에뮬레이터로 전해져오며 E3도 구현되기 시작했다. 2007년 PuTTY , 2011년 Red Hat , 2014년 Gnome...

[CSI Sequence] 화면 지우기

오늘은 지난 글 에 이어서 CSI Sequence를 이용해 화면을 지우는 방법을 알아보도록 하겠다. CSI Sequence에는 화면을 지우는 시퀀스가 2개 있다. 첫 번째는 EL이라고 불리는 Erase in Line 시퀀스다. CSI # K 로 구성된 시퀀스로 이름 그대로 줄을 지우는 데 사용되는데, # 을 주지 않으면 기본값은 0으로 처리되며, 값을 준다면 0, 1, 2 총 세개중 하나를 줘야 한다. 이외의 값이 오는 경우 시퀀스 자체가 무시된다. 예를 들어 0x311b5b334b32 즉, 1^[3K2 같은 값을 출력하면 ^[3K 는 무시되고 화면에 12 만 출력된다. 0, 1, 2의 동작은 정리하면 다음과 같다. 0 커서부터 줄 끝까지 지운다. 1 줄 시작부터 커서까지 지운다. 2 커서의 위치와 상관 없이 줄 전체를 지운다. 주의할 점은 EL 시퀀스는 커서의 위치를 옮기지 않는다는 것이다. 즉, 지금 쓴 줄을 지우고 새로운 줄을 쓰고 싶으면, EL 시퀀스와 캐리지 리턴 (\r)을 같이 사용해줘야 한다. 두 번째는 ED라고 불리는 Erase in Display 시퀀스다. CSI # J 로 구성된 시퀀스로 이름 그대로 화면을 지우는 데, EL과 마찬가지로 # 의 값에 따라 화면의 어디를 지울지를 결정한다. 0 커서를 기준으로 화면 끝까지 지운다. 1 화면 시작부터 커서까지 지운다. 2 커서의 위치와 상관 없이 화면 전체를 지운다. 3 스크롤 버퍼에 저장된 라인을 지운다, 기본적으로 EL과 같은 느낌이다. 기본값이 0인 것도 같고, 커서의 위치를 옮기지 않는 것도 같고, 정해진 값 이외의 값이 들어오면 무시하는 것도 같다. 단 하나의 차이는 EL에 없는 3이 추가된 것이다. 이 시퀀스는 스크롤 버퍼에 저장된 값을 지우는 시퀀스다. 원래 VT100 시절에 있던 시퀀스는 아니다. 그 시절 터미널에는 스크롤이라는 개념이 없었다. 이 시퀀스는 X 윈도우 시스템 과 ...

[CSI Sequence] 커서 옮기기

Code Abbr Name CSI # A CUU CUrsor Up CSI # B CUD CUrsor Down CSI # C CUF CUrsor Forward CSI # D CUB CUrsor Backward CSI # E CNL CUrsor Next Line CSI # F CPL CUrsor Previous Line CSI # I CHT Cursor Horizontal forward Tabulation CSI # Z CBT Cursor Backward Tabulation CSI # G CHA Cursor Horizontal Absolute CSI # ; # H CUP CUrsor Position 오늘은 지난 글에 이어 이번 글에서는 CSI sequence를 이용해 커서를 옮기는 방법에 대해 알아보겠다. 커서를 옮기는 CSI sequence의 종류는 위와 같이 정리할 수 있다. CUU, CUD, CUF, CUB 이들은 각각 CUrsor Up, CUrsor Down, CUrsor Forward, CUrsor Backward의 약자로 이름 그대로 커서를 위, 아래, 앞, 뒤로 이동한다. 인자로는 한 개의 숫자를 받고, 만약 인자가 생략되면 1로 취급된다. 즉, 0x1b[A 는 0x1b[1A 와 같은 의미이다. 이때 CUF나 CUB는 같은 줄 내에서만 움직이며 줄의 시작에서 CUB를 받아도 이전 줄로 움직이지 않고, 줄의 마지막에서 CUF를 받아도 다음 줄로 움직이지 않는다. 이런 동작을 원하면 각각 reverse auto-wrap 모드와 auto-wrap 모드를 켜야 하는데, 이 모드를 지원할지는 터미널별로 다르다. CNL, CPL 이들은 각각 Cursor Next Line, Cursor Previous Line을 의미하며, 커서를 다음 줄 혹은 윗줄로 옮긴다. CUU와 CUD는 커서의 column을 유지한 채 줄을 바꾼다면, CN...

CSI Sequence의 구조

^[ [78G|/- ^[ [17;79H\_ ^[ [9;1H ^[ [1P ^[ [79G|( ^[ [11;79H/ ^[ [K 임베디드 프로그래밍을 해본적 있다면, 임베디드 장치의 시리얼 포트를 연결했을 때, 위와 같은 문자열이 전송돼는 것을 보았을 것이다. 여기서 ^[ 로 시작하는 문자열은 ESC ( \0x1b )를 나타내는 특수한 제어문자로, 이 정체불명의 문자열의 정체는 터미널을 제어하는 escape code를 의미한다. escape sequence 중에서 [로 시작하는 시퀀스를 control sequence 혹은 CSI sequence라고 부른다. control sequence를 시작하게 하는 ESC [ 를 CSI (Control Sequence Introducer라고 부르기 때문이다. CSI sequence는 CSI 를 시작으로 @ , ` , 혹은 알파뱃으로 끝나는 일련의 시퀀스로, CSI 와 마지막 문자 사이에 임의의 개수의 파라미터로 구성되는데, 이때 파라미터는 ASCII로 정의 된 0 ( 0x30 )- 9 ( 0x39 )로 표현되며, 파라미터 사이는 ; ( 0x59 )로 구분된다. 즉, 위의 예제에서 ^[ [78G 는 숫자 78을 파라미터로 받는 G로 끝나는 CSI sequence를 의미하고, ^[ [17;79H 는 숫자 17과 79를 파라미터로 받는 H로 끝나는 CSI sequence를 의미한다. 이번 글에서는 ESC(0x1b)로 시작하는 CSI sequence의 개념과 기본적인 구조에 대해 살펴보았다. CSI sequence는 터미널의 커서 위치, 색상, 속성 등을 제어한다. 이를 활용하여 터미널에서 다양한 동작을 수행하는 프로그램을 개발할 수 있다. 다음 글에서는 CSI sequence의 종류와 각각의 기능에 대해 자세히 설명해보겠다.

텍스트 애플리케이션에서 Carriage Return 사용하기

Unix를 비롯한 대부분의 시스템은 LF ( \n , 0x0A )를 개행 문자로 사용하기 때문에 CR ( \r , 0x0D )을 사용하는 경우가 거의 없다. 텍스트 애플리케이션에서 진행표시줄을 만드는 것이 진행표시줄을 만드는 것이 현대 컴퓨터에서 CR 을 사용하는 거의 유일한 경우라고 볼 수 있다. CR 을 사용하면 터미널에서 진행표시줄을 간단하게 구현할 수 있다.

터미널 출력 제어를 위한 termios 구조체 이해하기

지난 글 에서 설명했듯이 OS X, 리눅스를 포함한 Unix를 이어받은 운영 체제는 LF (line feed, 0x0A , \n )를 개행 문자, 즉 커서를 다음 줄의 시작으로 옮기는 문자로 이용한다. 하지만 표준에 정의 된 LF 의 동작은 커서를 다음 줄로 내리는 것일 뿐, 커서를 줄의 처음으로 이동하지 않는다. 파일을 항상 운영 체제에 종속적인 애플리케이션을 통해서만 접근한다면 표준과 다른 동작은 문제되지 않는다. 하지만 파일과 입출력의 구분이 없는 유닉스 계열에서 파일과 프로세스의 입출력이 상호작용할 때 이 차이는 문제될 수 있다. 이 차이를 다루기 위해서 터미널은 출력에 적절한 가공을 하여 출력한다. 이를 제어하기 위한 플래그가 POSIX.1 표준이 정의 하는 termios 구조체의 c_oflag 다. c_oflag 는 터미널이 받은 문자를 출력하기 전에 어떤 후처리를 할지에 대한 플래그다. c_oflag에서 가장 중요한 플래그는 OPOST 다. 이는 입력에 대한 후처리를 할지 말지에 대한 플래그로 OPOST 가 꺼져있으면 다른 플래그와 상관없이 터미널은 받은 문자열을 그대로 보여준다. 이 플래그를 끄는 경우는 거의 없다. 하지만 터미널을 텍스트를 보여주기 위한 용도가 아닌 바이너리 데이터를 전송하기 위해 사용하는 경우 끄는 것이 좋다. 터미널이 Unix 계열 운영 체제에서 원하는대로 동작할 수 있게 해주는 플래그는 ONLCR 이다. ONLCR 이 켜져 있으면 터미널은 출력을 해석할 때 NL 을 CRNL 로 해석한다. 즉, Unix에서도 ONLCR 이 꺼져있다면, LF 를 만났을 때, 다음 줄의 처음으로 이동하는 것이 아닌, 현재 위치의 다음 줄로 이동한다. Unix 계열 운영 체제에서 윈도우에서 만들어진 파일을 출력해야 할 경우, CRNL 을 NL 로 바꾸지 않고도 ONLCR 플래그를 끄는 것 만으로도 간단하게 출력할 수 있다. 이외에도 구형 Mac OS 처럼 동작하게 해주는 OCRNL 플래그나 탭문자( 0x09 , \t )를...

LF, CR 그리고 CRLF

개행 문자는 멀티 플랫폼에서 작업하는 사람들을 귀찮게 하는 것 중 하나다. Mac OS, Windows, Linux는 모두 다른 문자를 개행 문자로 사용한다. 심지어 Mac OS는 과거 버전과 최신 버전에서의 동작이 또 다르다. 이번 글에서는 시스템별로 다른 개행 문자를 사용하는 원인을 살펴볼 것이다. ISO 6429 표준 에 따르면 LF (line feed, \n)는 커서를 현재 행을 유지하면서 다음 줄로 옮기고, CR (carriage return, \r)는 커서를 현재 라인의 처음으로 옮긴다. 우리가 원하는 개행 문자의 동작을 위해서는 CR 과 LF 를 함께 써야 한다. 이런 구분은 라인을 바꾸는 동작과, 커서를 처음으로 옮기는 동작이 구분된 초기 프린터나 타자기의 동작을 모방했기 때문이다. A B 즉, 표준에 따르면 " A\nB "라는 문자열은 위에 보인 예시대로 A 아래 바로 B 가 오는 것이 아니라, A 의 대각선 아래 B 가 와야 한다. 하지만 개행 문자로 CRLF 를 사용하는 시스템은 드물다. 지금은 스토리지가 매우 싼 자원이다. 하지만 과거에는 스토리지가 매우 비싼 자원이었다. 이 시절 시스템 설계자들은 개행 문자를 위해 2바이트나 할당하라는 것을 과도한 사치로 보았다. 결국 어떤 시스템 설계자는 LF 를 어떤 시스템 설계자는 CR 을 단독으로 개행 문자로 사용하기 시작했다. 그 와중에도 표준을 지키던 시스템이 있었다. 그중 하나는 CP/M 이라는 운영체계다. CP/M은 CR 과 LF 의 조합을 개행문자로 사용했다. 이는 단순히 표준을 지키고자 하는 의지 때문이 아니라, 과거 있었던 원격 터미널 장치와의 호환성을 유지하기 위한 전략적 선택이었다. 다른 말로 하면, 비싼 스토리지 비용을 감당하더라도 호환성을 유지해 시장을 장악하는 것이 유리하다는 판단이었다. 후에 나오는 몇몇 운영체제도 CP/M 과 같은 선택을 했고, 이 중 하나가 마이크로소프트의 MS-DOS였다. 이 선택이 지금까지 이어져 마이크로소프트의 ...

escape codes의 이해

escape code의 ISO 6429 에서 정의된 정확한 명칭은 control function이다. control function을 표현하는 코드 혹은 시퀀스를 흔히 escape code라고 말한다. ISO 6429 가 정의하는 컨트롤 코드는 모양에 따라 C0 코드와 C1 코드로 나누어진다. C0 코드는 ASCII Table에서 non-printing 문자에 해당하는 코드이다. 아마 Escape Codes 중 개발자들에게 가장 익숙한 코드일 것이다. 라인 피드( \n ), 캐리지 리턴( \r ), 탭( \t ), 널 문자( \0 ) 등이 여기에 해당한다. C1 코드는 0x80 에서 0x9F 사이 32개의 1바이트 값으로 표현되는 코드다. C0와 다르게 C1 코드는 ASCII 에 정의된 값이 아니다. ASCII 만 지원하는 터미널, 다시 말해 7비트 환경에서만 사용할 수 있고, 8비트 환경인 현대 터미널에서는 사용될 수 없다. 그래서 현대 터미널은 대부분 C1 코드가 필요할 때 escape sequence로 표현한다. escape sequence란 말 그대로 ESC 를 시작으로 하는 일련의 문자열을 의미한다. 그중 C1 코드와 같은 기능을 하는 시퀀스는 첫 바이트가 ESC ( 0x1B )이고 두 번째 바이트가 @ ( 0x40 )에서 _ ( 0x5F )로 이루어지는 두 바이트의 시퀀스다. 예를 들면 IND 는 8비트 환경에서는 0x84 로 표현되지만 7비트 환경에서는 ESC D ( 0x1B 0x44 )로 표현된다. 이 두 번째 바이트는 Final character of Escape sequence 라는 의미로 Fe 라고 불리며, 두 바이트로 표현된 C1 코드를 Fe sequence 라고 부르기도 한다. ISO 6429 가 정의하는 escape sequence는 Fe sequence 이외에도 더 있다. 예를 들면 ` ( 0x60 )에서 ~ ( 0x7E ) 사이의 값은 Fs 라고 불리며, ESC 다음 Fs 가 따라오는 시퀀스는 Fs sequenc...

Escape Codes의 역사

이미지
개발을 위해 컴퓨터에 리눅스를 설치할 때 반드시 설치하는 프로그램이 있다. sl 이라는 프로그램이다. 이게 무슨 프로그램이냐면 커맨드 창에서 sl 을 치면 기차를 보여주는 프로그램이다. 실용성 있는 프로그램은 아니고, 터미널에서 많이 사용되는 ls 명령어의 오타를 냈을 때 화면에 기차를 보여줘 생각할 시간을 주는 프로그램이다. 이를 통해 오타를 낼 정도로 마음이 급한 상황에서 다른 실수를 하지 않고 조금은 침착해지라는 의미에서 항상 설치한다. 출처: https://github.com/mtoyoda/sl 터미널은 실행한 프로그램이 내보내는 두 출력 stdout 과 stderr 를 받아 화면에 보여주는 프로그램이다. stdout 과 stderr 은 순차적인 출력이다. 보통의 출력은 왼쪽 위에서 오른쪽 아래로 흘러간다. sl 처럼 이미 쓰인 화면에 새로운 글자를 그리기 위해서는 특별한 방법이 필요하다. 이 특별한 방법은 escape codes 라고 한다. escape codes는 터미널에 정의된 일종의 약속이다. 이 약속은 현재는 ISO 6429 에서 정의하는 표준을 따른다. 하지만 과거에는 통일된 규정이 없이 터미널 모델마다 다른 규정을 가지고 있었다. 과거의 컴퓨터를 모르면 터미널 모델마다 다르다는 것이 이해되지 않을 수도 있다. 이를 알기 위해서는 컴퓨터의 역사를 알아야 한다. 지금은 터미널이라고 하면 CLI 를 위한 애플리케이션을 의미한다. 하지만 과거 터미널은 문자 그대로 컴퓨터의 최종 단말기였다. 이 단말기는 메인프레임 컴퓨터 에 연결돼 메인프레임 컴퓨터의 입출력을 담당했다. 현대의 터미널 앱이 터미널 에뮬레이터 라고 불리는 이유가 이 터미널을 에뮬레이트 하는 것이기 때문이다. 과거 터미널은 ADM-3A , 지금도 유명한 IBM 사의 IBM 2260 과 IBM 3270 , 후에 HP에 인수되는 DEC 사의 VT 시리즈 등 다양한 회사에서 개발됐고, 이들은 각각 고유의 표준을 가지고 터미널을 조정했다. 출처: https://en.wik...

[Rust] 반복자에게 할 일 더해주기 - Iterator adapters

다른 Iterator (a.k.a 반복자)를 받아 새로운 반복자를 반환하는 함수를 iterator adapter 라고 부른다. adapter라는 이름은 GoF의 디자인 패턴 중 하나인 adapter pattern 에서 온 말이라고 한다. 그런데 실제로는 adapter pattern보다는 decorator pattern 에 해당하기 때문에 이름을 신경쓰면 용도를 헷갈릴 수 있다. 그러니 이름에 대해서는 크게 신경 쓰지 않는 것이 좋다. 이름에 대한 불평은 그만하고 iterator adapter가 무엇을 하는가? iterator adapter는 반복자가 순회하면서 할 일을 더해준다. 무슨 말인지는 실제 구현체를 보면 쉽게 이해할 수 있을 것이다. map 함수는 대표적인 adapter다. 함수형 언어를 써본 사람이라면 많이 익숙할 map 함수가 반환하는 반복자는 기존의 값을 새로운 값으로 변환한 값을 순회한다. 이 외에도 표준 라이브러리에 이미 다양한 adapter가 구현돼있다. 이중 가장 많이 사용되는 것은 반복문과 같이 사용하기 좋은 adapter들이다. 조건을 만족하는 값만 돌려주는 filter , 지정 된 몇 개의 값만 반환하는 take , 반대로 몇 개의 값은 생략하고 반환하는 skip , 몇 개씩 값을 건너뛰며 순환하는 step_by 와 같은 adapter가 대표적인 예시다. 이런 adapter를 반복문과 함께 사용하면 복잡한 조건 처리를 쉽게 표현할 수 있다. 이 외에도 영원히 종료하지 않고 순환하게 만드는 cycle 이나, 순환하는 값에는 변환을 주지 않고, 사이드이펙트를 발생시키기만 하는 inspect 도 많이 사용된다. adapter를 사용할 때 주의해야 할 점이 하나 있다. adapter가 반환하는 것 역시 반복자일 뿐이다. 실제로 반복자를 사용하기 전에는 adapter가 지정한 일을 수행하지 않는다. 쉽게 말해 실제로 값이 필요할 때까지 실행을 미루는 lazy evaluation 을 한다는 것이다. 따라서 아래와 같은...

[Python] cache 데코레이터로 최적화하기

최적화는 귀찮다. 눈에 띄는 실수를 한 게 아니면 어떻게 고쳐야 할지 감이 오지도 않고, 대부분의 최적화는 가독성을 떨어뜨리기 때문에 버그가 발생할 확률이 늘어난다. 하지만 어떤 최적화 테크닉은 코드를 크게 수정하지 않고 큰 성능 향상을 가져온다. 메모이제이션 이 그 대표적인 예제다. 계산이 무겁거나, 디스크의 값을 읽거나, 네트워크 통신처럼 근본적으로 시간이 오래 걸리는 일은 그 실행 결과를 저장했다 재사용하는 것만으로 큰 성능향상을 가지고 온다. 파이썬은 메모이제이션을 쉽게 적용할 수 있는 데코레이터 를 제공한다. functools 모듈의 lru_cache 데코레이터 가 이것이다. 이 데코레이터를 붙이면 함수의 실행 결과를 캐싱해준다. 캐시의 크기는 maxsize 로 지정할 수 있다. 저장할 실행 값이 이 개수를 넘어가는 경우 LRU 알고리즘 에 따라 가장 오래전에 사용한 결과를 지우고 새 값을 캐싱한다. lru_cache 를 사용하면 쉽게 최적화할 수 있지만 아무 함수에나 사용할 수 있는 건 아니다. 함수의 인자를 캐시키로 사용하기 때문에 함수의 실행 결과가 함수의 인자 이외에 다른 요소에 의존적인 함수에는 사용하지 못한다. 즉, 랜덤 요소가 들어가거나 시간에 따라 결괏값이 변하는 함수에는 사용하면 안 된다. 결정성이 보장되는 함수에만 사용할 수 있다는 것은 모든 캐시의 공통적인 특성이다. 여기에 더해 파이썬이 제공하는 lru_cache 는 그 구현상의 문제로 한 가지 제약이 더 있다. 이 데코레이터는 값을 저장하기 위해 인자를 키로 가지는 dictionary 를 사용한다. 따라서 모든 인자가 hashable 타입이어야 한다. 다시 말해 mutable 하지 않은 dictionary, set, list 등을 인자로 받는 함수는 이 데코레이터를 사용해 캐싱할 수 없다. 이런 타입을 인자로 받던 함수는 그 인자를 frozenset 이나 tuple 같은 immutable 타입으로 변환해야 한다. 게다가 keyword argument 를...

Rust의 반복문

Java나 C++ 같은 언어에서는 조건 반복문 과 for-each 반복문 에 같은 for 키워드를 사용한다. 하지만, Rust 는 조건 반복문에는 while 키워드 을 for-each 반복문에는 for 키워드를 사용한다. Rust는 여기에 하나의 반복문을 더 제공한다. loop 반복문이다. 이는 while true 라고 쓰는 것과 같이 같은 코드를 무한히 실행한다. 실제로 무한 반복이 필요한 경우에도 사용되고, 반복문의 조건을 하나의 표현식으로 서술하기 힘들어 break 문으로 뺄 때에도 사용된다. 하지만 loop 가 while true 와 완전히 같은 코드는 아니다. loop 는 while 문과 다르게 그 자체로 값을 가진다. break 문 뒤에 값을 적으면 이 값이 반복문 전체의 값이 된다. 그렇다면 다른 반복문은 값을 가지지 않는데 loop 문만 값을 가지는 이유는 무엇일까? 일반적으로 반복문이 끝나는 데는 두 가지 조건이 있다. 주어진 조건이 끝나는 것과 break 문을 만나는 것이다. 평범한 반복문에서도 같은 문법을 써서 break 문을 만났을 때의 값은 정하게 할 수는 있지만, 조건이 끝나 반복문이 종료되는 경우 값을 지정할 수 없다. 그래서 일반적으로 반복문은 값을 가지지 않는다. 하지만 loop 문은 종료 조건이 없기 때문에 끝나기 위해서는 항상 break 문을 만나야 한다. 이런 특징 덕분에 loop 는 다른 반복문과 다르게 구문 자체가 값을 가질 수 있다. Rust에서는 for-each 반복문을 for-in 반복문이라고 부른다. 실제로 for value in values 라고 코드를 작성하기 때문이다. 그렇다면 여기서 values 에 들어갈 수 있는 값은 어떤 값일까? 내가 만든 타입을 for-in 반복문에 사용하고 싶으면 어떻게 해야 할까? 값을 순회할 수 있는 타입은 모두 for-in 반복문에 사용할 수 있다. Rust에서는 Iterator 가 순회할 수 있는 타입을 의미한다. 다시 말해 for-in 반복문은 Iter...

조건 반복문과 for-each 반복문

프로그램에서 반복문은 크게 두 가지로 나눌 수 있다. 하나는 특정 조건을 만족할 때까지 같은 코드를 반복하는 것이다. 포트란으로부터 시작해서 C를 거쳐 대부분의 프로그래밍 언어들은 기본적으로 이런 형태의 반복문을 제공한다. 예를 들면 C의 while 문이나 for 문이 그렇다. 이것들은 주어진 조건이 성립하는 동안 정해진 코드를 실행한다. 혹은 특정 범위의 모든 값에 대해 같은 코드를 실행하기도 한다. 이런 반복문은 C++에서는 range-based for loop , C#과 Java는 foreach 문 , JavaScript는 for ... of 문으로 부르는 등 다양한 이름으로 불린다. 이런 종류의 반복문을 부르는 이름은 언어마다 다르지만, 편의를 위해 이 글에서는 이것을 for-each 문으로, 임의의 조건을 걸 수 있는 반복문을 조건 반복문으로 부르도록 하겠다. for-each 반복문은 사실 조건 반복문을 특정한 조건에서만 사용할 수 있도록 특수화시킨 것으로 볼 수도 있다. 실제로 C++의 range-based for loop나 Rust의 for loop 는 조건 반복문의 syntactic sugar로 설명하기도 한다. C++이나 Java 등의 언어는 원래 for-each 반복문이 없었다. 그래서 조건 반복문을 이용해야 했기 때문에 off-by-one-error 같은 실수가 종종 발생했다. 이에 대한 해결책이 없었던 것은 아니다. 예를 들어 Lisp 같은 언어는 이미 for each 반복문을 가지고 있었다. 하지만 언어 스펙을 변경하는 것은 큰일이기 때문에 많은 언어들이 새 문법을 추가하지 않고 해결하고자 했다. C++의 이터레이터를 이용하는 algorithm 라이브러리 가 이런 경우다. 하지만 이런 해결책은 함수를 인자로 받는 higher-order function 을 쓰기 때문에 for 문을 쓸때보다 사용하기 불편하거나 코드를 읽기 어려워지는 경우가 있는 것은 사실이었다. 그래서 Java, C++, JavaScript ...

[Rust] 함수의 lifetime parameter는 언제 써야 하고 언제 생략할 수 있나요?

누군가가 나에게 러스트 가 다른 언어와 다른 가장 큰 차이가 뭐냐고 묻는다면, 라이프타임이라고 대답할 것이다. 그래서 러스트를 처음 배우는 사람들이 러스트의 적응하는데 가장 어려운 부분도 이것이라고 생각한다. 특히 함수의 경우 라이프타임을 표기하는 파라미터는 생략이 가능해서 이 생략 규칙에 익숙하지 않은 사람은 함수가 의미하는 것을 이해하지 못하기도 한다. 일단 함수를 정의할 때는 크게 신경 쓰지 않아도 된다. 러스트는 분석 도구가 매우 잘 만들어져 있다. 지금까지 나온 어떤 언어보다 잘 돼 있다고 말하고 싶을 정도다. 어떤 라이프타임을 생략할 수 있는지 확실치 않은 경우는 모든 레퍼런스에 파라미터를 적어주고 clippy 를 사용하면 어떤 파라미터가 필요 없는지 친절하게 알려준다. 이에 따라 코드를 다듬으면 된다. 문제는 다른 사람이 작성한 함수를 읽을 때이다. 라이프타임에 따라 함수의 의미가 달라진다. 때문에 라이프타임을 정확히 파악하지 못하면, 코드를 잘못 이해하는 경우가 생긴다. 생략된 라이프타임 파라미터가 어떤 값이 되는가는 생략된 라이프타임이 함수의 인자에 사용됐는가 결괏값에 사용됐는가에 따라 다르다. 우선 입력에 사용된 라이프타임이 생략된 경우는 간단하다. 전부 다른 파라미터로 생각하면 된다. 다시 말해서, 입력에 사용된 라이프타임은 다른 라이프 타임과 관계없다면 모두 생략할 수 있다. std::cmp::Ord::cmp(&self, other: &Self) -> Ordering 가 2개의 레퍼런스 타입을 받지만, 라이프타임이 명시되지 않은 이유가 이 때문이다. 결괏값에 사용된 라이프타임을 생략하는 것은 이보다 약간 더 복잡하다. 우선 출력에 사용된 라이프타임은 특정한 두 경우만 생략될 수 있다. 첫 번째 경우는 함수의 인자에 단 하나의 라이프타임만 사용된 경우이다. 함수의 인자에 라이프타임 파라미터가 하나만 사용된 경우, 결괏값에 생략된 라이프타임 파라미터는 전부 인자의 라이프타임으로 간주한다. 예를 들어 std...

이 블로그의 인기 게시물

USB를 이용한 전원 공급 (1) - USB BC

USB 3.x 케이블 무엇을 골라야 하나

USB 2.0 케이블의 내부 구조

USB를 이용한 전원 공급 (2) - USB PD

USB 2.0 과 3.x의 컨넥터 호환성