Skip to content

Latest commit

 

History

History
225 lines (158 loc) · 9.83 KB

Binaries (#8).livemd

File metadata and controls

225 lines (158 loc) · 9.83 KB

Binaries (#8)

도입부

엘릭서의 데이터 타입을 설명하는 글에서는 바이너리를 간략하게 다루었지만, 여기서는 더 자세히 설명합니다.

앞에서 언급했듯이 바이너리는 바이너리 데이터를 바이트 모음(binary) 또는 비트 모음(bitstring)으로 저장합니다. 표면적으로는 두 가지가 거의 동일하다고 생각하지만, 바이너리를 사용하면 바이트를 가장 작은 요소로 하는 데이터를 더 간단하게 처리할 수 있습니다.

바이너리 리터럴은 꺽쇠 괄호<<>>로 표현할 수 있으며, 그 안의 숫자는 단일 바이트의 값을 나타냅니다.

iex> <<1, 0, 4, 18>>
<<1, 0, 4, 18>>

따라서 바이너리 리터럴 <<1, 0, 4, 18>>은 0x01, 0x00, 0x04, 0x12 값을 가진 4바이트를 나타냅니다.

바이트 0에서 255 사이의 값만 저장할 수 있으므로 255를 초과하는 바이트 값은 의미가 없습니다. 255를 초과하는 값을 지정하면 어떻게 되는지 확인해 보았는데, 주행 거리계처럼 값이 0으로 돌아오고 거기서부터 올라가는 것을 확인할 수 있었습니다.

iex> <<1, 2, 255, 256>>
<<1, 2, 255, 0>>
iex> <<1, 2, 255, 260>>
<<1, 2, 255, 4>>

바이트 값은 10진수 8진수(0o) 또는 16진수(0x) 표기법을 사용하여 지정할 수 있습니다. 다음은 예시입니다.

iex> <<1, 0, 0o04, 0x12>>
<<1, 0, 4, 18>>

IEx는 항상 10진수 바이트 값을 사용하여 바이너리 값을 표시합니다.

바이너리는 <> 연산자를 사용하여 연결할 수 있습니다.

iex> <<3, 12>> <> <<4, 8>>
<<3, 12, 4, 8>>

byte_size 함수는 바이너리 값의 바이트 수를 검색하는 데 사용할 수 있습니다.

iex> byte_size(<<1, 0, 4, 18>>)
4
iex> byte_size(<<1, 2, 128, 96, 255, 4>>)
6

변수를 사용하여 바이너리 리터럴로 바이트 값을 지정할 수도 있습니다.

iex> byte1 = 34
34
iex> byte2 = 154
154
iex> binary = <<byte1, byte2, 87>>
<<34, 154, 87>>

비트스트링

비트스트링은 바이트 대신 비트 모음으로 동작한다는 점을 제외하면 매우 유사합니다. 비트스트링 리터럴은 거의 동일하게 보입니다. 전체 바이트는 10진수(혹은 8진수 혹은 16진수)로 지정할 수 있으며, 비트 시퀀스는 ::size 연산자를 사용하여 지정할 수 있습니다. 따라서 <<2::size(2)>>는 비트 문자열 "10", <<5::size(3)>>는 비트 문자열 "101"이 됩니다. 더 긴 비트 시퀀스를 지정하기 위해 높은 숫자를 사용할 수도 있습니다. 비트 문자열 <<1138::size(11)>>10001110010과 같습니다.

IEx에서 비트스트림 리터럴이 어떻게 보이는지 살펴보겠습니다.

iex> <<2::size(2)>>
<<2::size(2)>>
iex> <<5::size(3)>>
<<5::size(3)>>
iex> <<1138::size(11)>>
<<142, 2::size(3)>>

마지막 예제인 <<1138::size(11)>>를 살펴 겠습니다. IEx에서는 <<142, 2::size(3)>>로 표시됩니다. 이 두 값은 서로 다른 방식으로 표시될 뿐 동일합니다. 142는 앞 8비트인 10001110에 해당하며, 2::size(3)은 나무지 3비트인 010에 해당합니다.

따라서 IEx는 항상 8비트의 모든 청크에 대해 바이트 값을 표시하고 ::size 표기법을 사용하여 나머지 비트를 표시하는 것을 볼 수 있습니다.

::size 연산은 비트 모양에 큰 차이를 만듭니다. 값에 필요한 크기보다 큰 크기를 지정하면 앞의 비트가 0으로 설정됩니다. 따라서 <<5::size(5)>>00101 비트스트링을, <<5::size(3)>>101 비트스트링을 생성합니다.

값에 필요한 크기보다 작은 크기를 지정하면 최상위 비트가 잘립니다. 따라서 <<5::size(2)>>는 앞의 1이 잘려 나갔으므로 비트스트링 01이 됩니다. IEx는 결과로 비트스트링 "<<1::size(2)>>"가 표시되는데, 이는 01에 해당합니다.

바이너리는 비트스트링의 서브타입이므로 모든 바이너리는 비트스트링입니다. 비트스트링은 비트 수가 8로 나눠질 경우에만 바이너리로 간주합니다. 이는 "::size" 옵션을 사용하여 지정하는 경우에도 마찬가지입니다. 따라서 <<4::size(8)>><<4::size(16)>>은 비트스트링이면서 바이너리이지만, <<4::size(7)>>은 비트스트링일 뿐입니다.

엘릭서는 is_binaryis_bitstring 함수를 제공합니다.

이 비트스트링은 비트 수를 8로 나눌 수 없기 때문에 바이너리가 아닙니다.

iex> is_binary(<<142, 2::size(3)>>)
false
iex> is_bitstring(<<142, 2::size(3)>>)
true

모든 바이너리는 비트스트링이기도 합니다.

iex> is_bitstring(<<142, 2>>)
true
iex> is_binary(<<142, 2>>)
true

8로 나눌 수 있는 비트스트링은 바이너리이지만 그렇지 않은 것은 그냥 비트스트링입니다.

iex> <<4::size(8)>>
<<4>>
iex> is_binary(<<4::size(8)>>)
true
iex> is_bitstring(<<4::size(8)>>)
true
iex> is_binary(<<4::size(16)>>)
true
iex> is_bitstring(<<4::size(16)>>)
true
iex> is_binary(<<4::size(7)>>)
false
iex> is_bitstring(<<4::size(7)>>)
true

문자열은 바이너리이므로 문자열은 바이너리인 동시에 비트스트링이기도 합니다.

iex> is_binary("This is a string")
true
iex> is_bitstring("This is a string")
true

다른 데이터 타입은 바이너리도 비트스트링도 아닙니다.

iex> is_binary(1.2)
false
iex> is_bitstring(1.2)
false
iex> is_binary(5)
false
iex> is_bitstring(5)
false
iex> is_binary(:atom)
false
iex> is_bitstring(:atom)
false

저는 바이너리와 비트스트링을 같은 개념의 두 가지 측면으로 간주합니다. 그들은 바이너리 비트 blob을 표현하는 다른 방법일 뿐입니다.

::size 연산자를 세그먼트 옵션이라고 하며 blob을 지정하는 데 사용할 수 있는 더 많은 세그먼트 옵션이 있습니다.

::float 세그먼트 옵션을 사용하면 숫자를 부동 소수점 숫자로 변환한 다음

iex> <<4.3::float>>
<<64, 17, 51, 51, 51, 51, 51, 51>>
iex> <<3::float>>
<<64, 8, 0, 0, 0, 0, 0, 0>>

::utf8 세그먼트 옵션은 문자열을 UTF-8 바이트의 바이너리로 변환합니다. 그러나 문자열은 이미 UTF-8 바이트의 시퀀스이므로 표준 엘릭서 문자열에는 별다른 효과가 없습니다. 인코딩을 변환하는 데 더 유용할 수 있습니다. ::utf8 옵션은 문자열 리터럴로 제한됩니다.

iex> <<"Bob"::utf8>>
"Bob"

::utf16 세그먼트 옵션을 문자열을 UTF-16 바이트의 바이너리로 변환합니다. IEx는 UTF-16 바이너리를 텍스트로 표시하려고 하지 않으므로 결과를 바이너리 리터럴로 표시합니다. ::utf16 옵션은 문자열 리터럴로 제한됩니다.

iex> <<"Bob"::utf16>>
<<0, 66, 0, 111, 0, 98>>

이러한 세그먼트 옵션에는 더 많은 옵션이 있으며, Kernel.SpecialForms 문서의 비트스트림 생성자 구문 섹션의 일부로 문서화되어 있습니다. 바이너리로 변환해야 할 때 여러 가지 옵션이 있습니다.

바이너리 리터럴에 문자열을 포함할 수도 있으며, 문자열 바이트가 바이너리에 추가됩니다.

iex> <<3, 5, "Bob", 2, 10>>
<<3, 5, 66, 111, 98, 2, 10>>

엘릭서 바이너리, 비트스트림, 그리고 그것들이 어떻게 표현되는지 이해하는 데 시간이 조금 걸렸는데, 이 글을 쓰는 것만으로도 엘릭서를 더 잘 이해하는 데 도움이 되었습니다. 또한 IEx에서 여러 가지를 시도해보는 것만으로도 더 잘 이해할 수 있었습니다. 엘릭서에 익숙해지면 IEx에서 여러 가지를 해보면서 작동 원리를 파악하는 것을 추천합니다.

대부분의 엘릭서 코드는 바이너리를 직접 처리할 필요가 없을 것입니다. 문자열도 바이너리라는 의미에서 바이너리가 존재하지만, 바이너리로 취급할 필요는 없을 것입니다. 엘릭서 코드에서 특정 종류의 바이너리(이미지, 오디오, 비디오 등) 데이터로 처리할 일이 있는 분들만 바이너리에 대해 자세히 알아보게 될 것입니다. 물론 엔디안과 같은 문제도 다루어야 할 것이지만, 지금 이 시점에서 그렇게 깊이 파고들지는 않겠습니다.

바이너리 조작을 많이 하지는 않겠지만, 바이너리 데이터가 꽤 흥미롭기 때문에 여기서 자세히 설명했습니다. 아마도 프로그래밍 초창기에 이미지 데이터, 화면 버퍼, 다양한 엔디안, 다양한 하드웨어에서 다양한 픽셀 색상 형식을 다루는 코드를 C++로 작성하던 시절로 거슬러 올라갑니다. 하드웨어 세부 사항(혹은 수동 메모리 관리)에 대해 더 이상 걱정할 필요가 없다는 것은 좋은 점이지만, 그런 것들을 배웠기 때문에 제가 사용하는 소프트웨어 라이브러리에서 어떤 일이 벌어지고 있는지 훨씬 더 잘 이해할 수 있게 되었습니다.