라벨이 C인 게시물 표시

2018년 19번째 주

이 포스팅은 그냥 지난 한 주간 읽었던 것들을 정리하는 포스트입니다. 그냥 예전에 봤던 글 중 나중에 필요한데 뭐였는지 기억 안 나는 글들이 있어서 쓰기 시작했습니다. 보통 하는 일과 관련된 글들이 올라오겠지만 딱히 정해둔 주제는 없고, 그때그때 관심 있었던 것을 읽었기 때문에 지난주에 쓰인 글일 수도 있고 몇 년 전에 쓰인 글일 수도 있습니다. C Primer Primer를 입문서라고 번역하는 게 맞는지 모르겠지만, 어쨌든 C 입문서라고 이름 붙인 문서 중에서 가장 마음에 든다. 다른 언어는 잘 추상화된 모델을 다루는 것이 중요하지만 C는 아니라고 생각한다. C는 내가 사용한 코드가 어떻게 변환되어 실행되는지 기계 단위로 이해하고 있어야 한다. 그럴 필요가 없는 상황에서는 C가 아닌 다른 언어를 써야 한다. C Is Not a Low-level Language C가 low-level 언어 는 아니라는 글이다. 전통적으로 low-level 언어는 머신에 대한 추상화 없이 기계어와 일대일 대응되는 언어를 의미한다. 당연히 이런 의미에서 C는 low-level 언어가 아니다. C는 나름의 abstract machine 을 가지고 있다. 특히 C11 이후로는 멀티 쓰레드 에 대한 개념도 abstract machine에 들어갔기 때문에 전통적인 의미에서 low-level 언어는 아니다. 그렇다고 해서 C가 다른 high-level 언어 와 같다는 의미는 아니다. 다른 high-level 언어들은 실제 기계어로 어떻게 번역되는지 몰라도 될 정도로 abstract machine을 정의한다. 하지만 C는 아니다. 사실 나는 C의 abstract machine도 기계를 몰라도 사용할 수 있을 정도로 잘 정의돼있다고 생각한다. 문제는 C를 사용하는 사람들이 실제 기계에서 어떻게 돌아가는지 고려하면서 코드를 작성한다. 이건 C언어 커뮤니티의 문제는 아니다. 사실 기계 레벨에서 어떻게 돌아가는지 고려하지 않아도 되는 프로젝트에서 C를 사용하는 것은 얻는 것에

2018년 16번째 주

이 포스팅은 그냥 지난 한 주간 읽었던 것들을 정리하는 포스트입니다. 그냥 예전에 봤던 글 중 나중에 필요한데 뭐였는지 기억 안 나는 글들이 있어서 쓰기 시작했습니다. 보통 하는 일과 관련된 글들이 올라오겠지만 딱히 정해둔 주제는 없고, 그때그때 관심 있었던 것을 읽었기 때문에 지난주에 쓰인 글일 수도 있고 몇 년 전에 쓰인 글일 수도 있습니다. ISO week date 오늘이 올해의 몇 번째 주인지 정하는 기준은 말하는 사람마다 다르다. 그래서 이 요약정리 시리즈는 ISO 8601 기준으로 몇 번째 주인지 표기한다. ISO 8601에 따르면 1월 4일이 포함된 주를 그 해의 첫 번째 주로 취급한다. 다른 말로 한 주의 시작을 월요일로 봤을 때 4일 이상 포함된 첫 번째 주가 그 해의 첫 번째 주인 것이다. TextQL 머신 러닝이나 데이터 마이닝을 할 때 데이터의 경향성을 대강 파악하고 싶을 때가 있다. 이럴 때 사용되는 데이터는 보통 매우 큰 데이터이기 때문에 에디터로 열어서 보는 것은 무리가 있고, 간단하게 스크립트를 짜서 실행하게 된다. 근데 이때 하는 일은 대부분 비슷한 일이기 때문에 꽤나 귀찮은 작업이었는데, 스크립트를 짜지 않고 파일을 SQL 을 실행시켜주는 프로젝트가 있었다. 아직 사용해보지는 않아서 직접 스크립트를 짜는 것과 비교하면 어떨지는 모르겠지만 꽤 많은 사람이 쓰고 있는 것 같다. Writing 수학을 배워야 하는 이유가 사고하는 법을 익히기 위해서이듯이, 글쓰기를 배우는 이유는 무엇을 생각했는지 명확하게 하기 위해서다. 사람은 모든 것을 기억할 수 없기 때문에 자신이 생각한 것을 써서 정리해야 한다는 것이다. 게다가 글을 쓰는 것은 내가 글 쓰는 주제에 대해 나보다 모르는 독자를 가정하고 글을 쓰게 된다. 그래서 평소에는 당연하게 생각하고 넘어갔던 것들에 대해서도 한 번 더 생각하고 넘어갈 시간을 가지게 한다. How quantum computing could wreak havoc on cryptocurren

[C] tagged pointer - 포인터에 정보 담기

Tagged pointer는 메모리 크기를 줄이기 위한 고전적인 테크닉이다. 기본적인 아이디어는 포인터의 모든 값이 의미 있는 값은 아니라는 것이다. 예를 들어 4 byte 단위로 align 되는 객체의 32-bit 포인터를 생각해보자. 그렇다면 이 객체의 주소는 4로 나누어 떨어지는 값이 돼야 하니 LSB(Least Significant Bit) 으로 부터 2 bit은 언제나 0b00 으로 고정될 것이다. 그렇다면 이 2 bit을 다른 정보를 담는 데 써도 아무 문제가 없다. 조금 더 구체적으로 경우 포인터의 값이 0x5678FFF0 , 0x5678FFF1 , 0x5678FFF2 , 0x5678FFF3 인 경우 모두 0x5678FFF0 에 있는 객체를 가리키도록 하고, 0x5678FFF4 , 0x5678FFF5 , 0x5678FFF6 , 0x5678FFF7 인 경우 모두 0x5678FFF4 를 가리키는 포인터로 해석하는 것이다. Tagged pointer를 만드는데 LSB 만 쓸 수 있는 건 아니다. 보통 user space에서 쓸 수 있는 최대 메모리가 제한돼 있다. 예를 들어 32-bit 윈도우에서 user space는 최대 3GB 까지 늘릴 수 있지만 , 기본적으로 2GB이다. 즉, MSB(Most Significant Bit) 1 bit를 tag에 쓸 수 있다. 64-bit 리눅스라면, 프로세스당 최대 메모리 스페이스는 256 TB까지 이므로 48 bit만 사용된다. 즉, MSB로부터 16 bit를 tag에 사용할 수 있다. 하지만 위의 두 예시에서 보았듯이 tag에 이용할 수 있는 MSB의 크기는 시스템별로 다르다. 따라서 MSB를 tagged pointer로 사용하는 경우 portable 한 코드를 만들기 어려워진다. Tagged pointer를 모든 포인터에 일반적으로 적용하지 않아도 된다. 그보다는 테이블같은 것에 저장할 포인터에만 사용하거나 포인터를 리턴하는 함수에 대해서만 사용하는 것이 일반적이다. 특히 포인터와 추가 정보를 리턴하

멀티 쓰레드 환경에서 fork는 조심해야 한다.

리눅스나 유닉스 같은 POSIX 시스템에서는 fork 를 이용해서 자신과 똑같은 프로세스를 만들 수 있다. 이때 fork 를 호출한 프로세스를 부모 프로세스 라고 하고, 새로 생성된 프로세스를 자식 프로세스 라고 부르는데, 자식 프로세스는 부모 프로세스의 모든 메모리를 복사한다. fork 는 그 뒤 exec 을 해서 다른 바이너리를 실행시키는 fork-exec 이 일반적인 사용법이다. 하지만 자식 프로세스 는 부모 프로세스 와 완전히 같은 메모리를 가지기 때문에, 스레드가 존재하지 않던 시절에는 exec 을 하지 않고 병렬 처리를 하기 위해서도 자주 사용되었다. 지금은 스레드를 사용하는 것이 더 사용하기 쉽고 가벼운 방식이기에 스레드를 병렬처리를 위해 스레드를 주로 사용하지만, 스레드보다 서로 간에 독립적이기 때문에 일을 분리하기 위해서 사용하기도 한다. 분명히 fork 는 없어서는 안 될 기능이지만 fork 에는 태생적 한계가 있다. 애초에 fork 는 스레드라는 개념이 존재하지 않던 시절에 만들어졌기 때문에 thread-safe 하지 않다. 따라서 멀티스레드 환경에서 사용하려면 조심해서 사용해야 한다. fork 가 멀티스레드 환경에서 문제를 일으키는 이유는 fork 가 부모 프로세스 의 메모리를 전부 복사하지만, fork 를 호출한 스레드를 제외한 나머지 스레드들은 죽어버리기 때문이다. 따라서 fork 를 호출한 스레드 이외의 스레드에서 획득한 자원은 아무것도 해제되지 않는다. 메모리 릭도 충분히 문제지만, 이는 단순한 메모리 릭만을 의미하지 않는다. fork 가 복사한 메모리에는 힙이나 스택 이외에 mutex 나 condition variable 들도 포함된다. 만약 mutex 나 condition variable 이 다른 스레드에서 사용된 채로 fork 된다면 해당 변수로 보호되는 critical section에는 다시는 진입할 수 없게 된다. 특히나 malloc 같은 thread-safe 한 함수는 대부분 내부적으로 글로벌한 m

빈 객체 크기는?

위와 같은 클래스를 생각해 보자. 보통 empty 클래스라고 부르는 이 클래스는 아무런 내부 변수를 가지고 있지 않다. 그렇다면 이 empty 클래스의 크기는 얼마일까? 언뜻 생각해보면, 아무런 멤버 변수가 없으니 그 크기가 0일 것 같다. 하지만 Java, C#, C(이 경우는 struct), C++ 어떤 언어에서도 0이 나오지 않는다. 이는 두 다른 객체가 같은 주소를 가르치는 일이 없도록 하기 위한 것 이다. empty 클래스는 보통 32 bit 환경에서는 1 바이트 크기를 가지고, 64 bit 환경에서는 2 바이트 크기를 가진다. 하지만 정확히 어떤 값이 나오는지는 알 수 없다. 스펙에 따르면 크기가 0이 되지 않기만 하면 된다. 정확한 크기는 구현체에 따라서 다르다.

C는 C++의 부분집합이 아니다

오늘 황당한 글을 봤다. 잘 짜인 C 프로그램은 C++ 프로그램이다. 따라서 잘 짜인 C 프로그램은 C++ 컴파일러로 컴파일할 수 있어야 한다. 일단 저 말은 C++의 창시자인 비야네 스트롭스투룹 이 한 말이다. 하지만 저 말은 틀린 말이다. "네가 뭔데 감히 비야네님을 틀리다고 하느냐"라는 생각이 들겠지만 잠시만 진정하자. 나는 비야네님이 틀렸다고 하지 않았다. 내가 틀리다고 하는 것은 아무런 문맥도 없이 그냥 저 문구만 따와서 말하는 사람을 틀리다고 하는 것이다. 저 말은 분명히 1999년 이전까지는 맞았던 말이다. 분명히 비야네 스트롭스트룹은 C++을 만들면서 C와의 호환성을 고려하였고, 당시의 표준(ANSI C)을 잘 지킨 C 코드는 C++ 컴파일러로 정상적으로 컴파일 되었다. 하지만 그것은 어디까지나 C99가 나오기 전의 이야기다. C99에서는 여러 가지 새로운 기능을 도입하였고, C++은 그것을 이미 다른 방식으로 구현하고 있었거나, 혹은 필요하지 않은 기능이라고 생각하여 가지고 오지 않았다. 게다가 새로운 표준인 C11이 나오고, C++도 새로운 표준인 03, 11을 거쳐 14까지 나오면서 둘 사이의 간극은 이미 어떻게 할 수 없을 정도로 커졌다. 그런 연유로 비야네 스트롭스트룹은 잘 짜인 C 프로그램이 C++ 프로그램이라고 말할 때 조건을 붙인다. "단, 이건 C89에 한정한다."라고. 하지만 요새 C89를 쓰는 프로그램이 얼마나 있나? 액티브하게 작업이 진행되는 프로젝트 중에서 C89를 쓰는 프로그램 있으면 가지고 와봐라. 찾으려고 노력해본 적은 없지만 찾기 어려울 것이다. 따라서 요새 저런 말을 하는 사람은 그냥 공부를 안 한 사람이다. 그것도 한 20년 전에 공부했던 사람이니 대선배님일 수도 있겠다. 그 사람에게 C99 이후 C++과 스펙이 변경되어 C99 표준을 지킨 코드는 C++컴파일러로 컴파일 안 될 수도 있다고 했더니, 거기에 달린 답변은 더 황당했다. 표준이 문제가 아니라 잘

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

RAII는 무엇인가

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

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

[Web] SpeechSynthesis - TTS API