기본 타입과 구조체(Struct), 시퀀스(Sequence)
DDS(Data Distribution Service) 미들웨어 아키텍처에서 데이터 타입의 정의는 단순히 정보를 담는 그릇을 만드는 행위를 넘어, 전체 분산 시스템의 상호운용성(Interoperability)과 실시간 성능(Real-time Performance)을 결정짓는 가장 기초적이면서도 핵심적인 설계 과정이다. 이기종 하드웨어와 다양한 운영체제, 그리고 서로 다른 프로그래밍 언어로 작성된 애플리케이션들이 네트워크를 통해 데이터를 주고받기 위해서는, 송신 측과 수신 측이 데이터의 물리적 구조와 논리적 의미에 대해 완벽하게 합의해야 한다. 이러한 합의를 기술적으로 보장하는 것이 바로 OMG(Object Management Group)의 IDL(Interface Definition Language) 표준이다.
IDL은 특정 프로그래밍 언어에 종속되지 않는 중립적인 명세 언어로, 시스템 설계자는 이를 통해 데이터 모델을 추상화하여 정의한다.1 이렇게 정의된 IDL 파일은 전처리 및 컴파일 과정을 거쳐 C, C++, Java, C#, Python 등 각 대상 언어에 맞는 소스 코드로 변환된다. 이 과정에서 가장 기본이 되는 구성 요소가 바로 기본 타입(Primitive Types), 구조체(Structure), 그리고 시퀀스(Sequence)이다. 본 절에서는 이들 각 요소의 문법적 특징부터 메모리 매핑 전략, 직렬화(Serialization) 메커니즘, 그리고 시스템 설계 시 고려해야 할 성능적 함의까지 심도 있게 분석한다.
1. 기본 타입 (Primitive Types): 데이터 모델의 원자(Atom)
DDS 데이터 모델링의 가장 기초 단위인 기본 타입은 더 이상 분해될 수 없는 원자적 데이터를 의미한다. IDL 표준은 이기종 시스템 간의 통신을 전제로 하므로, 각 기본 타입에 대해 플랫폼 독립적인 비트 크기와 값의 범위를 엄격하게 규정하고 있다. 이는 C나 C++의 표준 타입(int, long 등)이 컴파일러나 CPU 아키텍처(32비트 vs 64비트)에 따라 가변적인 크기를 갖는 것과는 대조적이다. DDS 설계자는 이러한 IDL 기본 타입의 특성을 정확히 이해하고, 각 데이터의 물리적 특성에 가장 부합하는 타입을 선정해야 한다. 잘못된 타입 선정은 불필요한 네트워크 대역폭 낭비나 데이터 오버플로우, 정밀도 손실 등의 치명적인 문제를 야기할 수 있다.2
1.1 정수형 타입 (Integer Types)과 아키텍처 독립성
정수형 타입은 소수점이 없는 수치를 표현하며, 부호의 유무(Signed/Unsigned)와 비트 크기에 따라 세분화된다. DDS 미들웨어는 CDR(Common Data Representation) 포맷을 사용하여 이러한 정수들을 직렬화하며, 이때 바이트 순서(Endianness)는 전송 시점의 설정을 따르되 헤더 정보를 통해 수신 측에서 자동 보정된다.
1.1.1 ) 16비트 정수: Short와 Unsigned Short
short와 unsigned short는 2바이트(16비트) 정수를 표현한다.
- 범위 및 특징:
short는 -32,768에서 32,767까지,unsigned short는 0에서 65,535까지의 값을 가진다. - 설계 전략: 현대의 64비트 CPU 환경에서도 16비트 정수는 여전히 유효한 가치를 지닌다. 특히 센서 네트워크나 IoT 환경에서 수천, 수만 개의 센서 데이터를 하나의 패킷으로 묶어 전송할 때, 32비트 대신 16비트를 사용하면 데이터 페이로드를 정확히 절반으로 줄일 수 있다. 예를 들어, 차량의 RPM, 속도, 엔진 온도 등은 대부분 16비트 범위 내에서 표현 가능하므로
short를 사용하는 것이 대역폭 효율적이다. - 매핑: C/C++에서는
short,unsigned short(또는int16_t,uint16_t)로, Java에서는short로 매핑된다. Java에는unsigned타입이 없으므로unsigned short를 수신할 때int로 승격(Promotion)하여 처리해야 데이터 손실을 막을 수 있음을 유의해야 한다.2
1.1.2 ) 32비트 정수: Long과 Unsigned Long
IDL의 long 타입은 C/C++ 개발자들에게 가장 큰 혼동을 주는 타입 중 하나이다.
- IDL long의 불변성: 표준 C++에서
long타입은 플랫폼에 따라 32비트(Windows, Linux 32bit) 또는 64비트(Linux 64bit)로 달라질 수 있다. 그러나 IDL의long은 항상 32비트(4바이트) 정수를 의미한다.2 이는 -2,147,483,648에서 2,147,483,647까지의 범위를 갖는다. - 설계 전략: 일반적인 수치 연산, 인덱스, IP 주소(IPv4), 시스템 상태 코드 등을 표현하는 표준적인 정수 타입으로 사용된다. 64비트 시스템에서도 32비트 정수는 메모리 정렬(Alignment)과 캐시 효율성 면에서 균형 잡힌 성능을 제공한다.
- 매핑 주의사항: 64비트 리눅스 환경에서 C++ 코드를 작성할 때, IDL
long멤버에 네이티브long변수(64비트)를 대입하면 상위 32비트가 잘려나가는(Truncation) 오버플로우가 발생할 수 있다. 따라서 C++ 매핑 시에는int32_t나 DDS 벤더가 제공하는DDS_Long타입을 명시적으로 사용하는 것이 안전하다.2
1.1.3 ) 64비트 정수: Long Long과 Unsigned Long Long
대용량 데이터 처리와 고정밀 시간 표현을 위한 8바이트 정수이다.
- 범위 및 특징: 약 ±900경(9x10^18)에 달하는 광범위한 값을 표현할 수 있다.
- 주요 용도:
- 고유 식별자(UUID): 분산 시스템 전역에서 유일성을 보장해야 하는 객체 ID나 트랜잭션 ID 생성 시 사용된다. 충돌 확률을 극도로 낮출 수 있다.
- 고정밀 타임스탬프: 나노초(Nanosecond) 단위의 시간을 1970년 에포크(Epoch) 기준으로 표현할 때 32비트 정수는 이미 범위를 초과했다.
long long은 수백 년 간의 나노초 시간을 표현하기에 충분하다. - 대규모 카운터: 네트워크 패킷 카운트나 스토리지 용량(바이트 단위) 계산 등 42억을 초과하는 수치에 필수적이다.
- 매핑: C++11 이상에서는
int64_t,uint64_t로 매핑되며, Java에서는long으로 매핑된다. IDLunsigned long long은 Javalong의 범위를 초과하므로, Java에서 이를 다룰 때는BigInteger클래스를 사용하거나 부호 비트를 무시한 비트 연산을 수행해야 한다.2
1.1.4 ) 8비트 정수: Int8과 Uint8 (DDS-XTypes의 진화)
초기 IDL(CORBA 시절)에는 8비트 정수를 표현하기 위한 전용 타입이 부재하여 octet이나 char를 대신 사용하는 편법이 난무했다. 그러나 이는 ’문자’와 ’수치’의 의미론적 혼동을 야기했다.
- 표준화: 최신 IDL 4.2 및 DDS-XTypes 표준은
int8(부호 있는 8비트 정수)과uint8(부호 없는 8비트 정수)을 명시적으로 지원한다. - 용도: -128 ~ 127 범위의 소규모 수치(예: 제어기 게인 값, 소형 모터의 각도 스텝)를 표현할 때
short대신 사용하여 메모리를 50% 절감할 수 있다. - 호환성 주의: 구형 DDS 미들웨어나 레거시 시스템과의 연동 시
int8/uint8을 인식하지 못할 수 있다. 이 경우 부득이하게octet으로 정의하고 애플리케이션 레벨에서 캐스팅하거나short로 확장해야 한다.4
1.2 부동 소수점 타입 (Floating-Point Types): 실수의 표현
실수 데이터를 처리하기 위한 타입으로, IEEE 754 표준을 준수하여 이기종 간의 부동 소수점 표현 차이를 극복한다.
1.2.1 ) Float (단정밀도)
- 크기: 32비트 (4바이트).
- 정밀도: 약 7자리의 유효숫자를 가진다.
- 활용: 일반적인 센서 데이터(온도, 습도, 압력 등)나 그래픽 렌더링을 위한 좌표계 데이터 처리에 가장 널리 사용된다. 대다수의 FPU(Floating Point Unit)가
float연산에 최적화되어 있어double대비 연산 속도가 빠르고, SIMD(Single Instruction Multiple Data) 명령어를 통한 병렬 처리에 유리하다.
1.2.2 ) Double (배정밀도)
- 크기: 64비트 (8바이트).
- 정밀도: 약 15~17자리의 유효숫자를 가진다.
- 활용: GPS 위성 좌표(경도/위도), 미사일 궤적 계산, 금융 파생상품 가격 산정 등 높은 정밀도가 요구되는 분야에서 필수적이다.
float사용 시 발생할 수 있는 누적 오차(Rounding Error)가 치명적인 결과를 초래할 수 있는 시스템에서는 반드시double을 사용해야 한다.
1.2.3 ) Long Double (확장 정밀도)의 딜레마
- 정의: IEEE 754의 확장 정밀도(Extended Precision)를 의미하며, 이론적으로 128비트를 지향한다.
- 이식성 문제(Portability Issues):
long double은 DDS 상호운용성에서 가장 골치 아픈 타입 중 하나이다. x86 아키텍처의 C/C++ 컴파일러는 이를 80비트로 구현하기도 하고, 일부 임베디드 컴파일러는double과 동일한 64비트로 처리하기도 하며, 최신 컴파일러는 128비트로 처리한다. - 권고 사항: 이러한 크기의 불일치는 송신 측(128비트)에서 보낸 데이터를 수신 측(80비트)에서 해석할 때 심각한 데이터 손상이나 정렬(Alignment) 오류를 유발한다. 일부 DDS 벤더(예: CycloneDDS)는 아예
long double지원을 명시적으로 제외하기도 한다. 따라서 이기종 통신이 필수적인 시스템에서는long double사용을 극도로 자제하거나,octet배열로 변환하여 수동으로 직렬화하는 방어적인 설계가 요구된다.2
1.3 문자와 옥텟: 텍스트와 바이너리의 경계
DDS IDL에서 char와 octet은 둘 다 8비트 크기를 갖지만, 그 용도와 미들웨어의 처리 방식은 완전히 다르다. 이 둘의 차이를 명확히 구분하는 것은 데이터 무결성을 위해 매우 중요하다.
1.3.1 ) Char: 문자의 표현
- 의미: 사람이 읽을 수 있는 텍스트의 구성 요소인 단일 문자를 의미한다.
- 인코딩: 기본적으로 ISO-8859-1(Latin-1) 문자 집합을 가정하지만, 현대의 시스템에서는 UTF-8의 1바이트 코드 유닛으로 해석되기도 한다.
- 매핑: C/C++의
char, Java의char(Java는 내부적으로 16비트이지만 IDLchar매핑 시 하위 8비트 사용)로 매핑된다. - 주의: 언어 매핑 과정에서 로케일(Locale) 설정에 따라 원치 않는 문자셋 변환이 일어날 수 있다.
1.3.2 ) Octet: 순수 바이너리 데이터
- 의미: “해석되지 않는 8비트(Opaque 8-bit)“를 의미한다. 미들웨어는
octet값에 대해 어떠한 인코딩 변환이나 검사를 수행하지 않고 비트 패턴 그대로 전송한다.2 - 활용: 이미지 파일(JPEG, PNG), 암호화된 데이터 블록, 직렬화된 객체, 네트워크 패킷의 헤더 등 바이너리 데이터를 전송할 때는 반드시
octet을 사용해야 한다. 만약 바이너리 데이터를char배열에 담아 전송하면, 특정 바이트 패턴이 ’NULL 문자’나 ’제어 문자’로 오인되어 문자열 함수에 의해 잘리거나 변조될 위험이 있다.2 - 매핑: C++에서는
uint8_t또는unsigned char, Java에서는byte로 매핑되어 부호 없는 8비트 처리를 보장한다.4
1.3.3 ) Wchar: 다국어 지원
- 의미: 유니코드 등 멀티바이트 문자를 표현하기 위한 와이드 문자(Wide Character)이다.
- 플랫폼 종속성: Windows의
wchar_t는 2바이트(UTF-16), Linux의wchar_t는 4바이트(UTF-32)로 크기가 다르다. DDS 미들웨어는 CDR 마샬링 과정에서 이를 네트워크 표준 크기(주로 설정에 따름)로 변환하여 상호운용성을 보장하지만, 이 변환 비용(CPU 오버헤드)을 고려해야 한다. 최근에는wchar대신 UTF-8로 인코딩된string을 사용하는 추세가 강하다.7
1.4 Boolean 타입
- 의미: 논리적 참(TRUE)과 거짓(FALSE)을 표현한다.
- 전송 포맷: 네트워크 상에서는 1바이트로 전송된다. 일반적으로 0은 FALSE, 0이 아닌 모든 값은 TRUE로 해석되지만, DDS 표준은 명확성을 위해 0과 1의 사용을 권장한다.
- 매핑: C++에서는
bool, Java에서는boolean으로 매핑된다. C 언어 매핑 시DDS_Boolean타입(주로unsigned char)으로 정의된다.2
1.5 종합 매핑 테이블 (IDL to Language Mappings)
다음은 주요 IDL 기본 타입이 각 프로그래밍 언어로 어떻게 매핑되는지, 그리고 설계 시 고려해야 할 비트 크기를 정리한 표이다.
| IDL Type | Bit Size | C Mapping (Classic) | C++11 Mapping (Modern) | Java Mapping | Python Mapping | 주요 활용 사례 |
|---|---|---|---|---|---|---|
| char | 8 | char | char | char | str (len 1) | ASCII 문자, 텍스트 플래그 |
| wchar | 16/32 | wchar_t | wchar_t | char | str (len 1) | 다국어 문자 |
| octet | 8 | unsigned char | uint8_t | byte | bytes | 이미지, 암호화 키, 바이너리 |
| boolean | 8 | unsigned char | bool | boolean | bool | 상태 플래그, 스위치 ON/OFF |
| short | 16 | short | int16_t | short | int | RPM, 속도, 소형 센서값 |
| unsigned short | 16 | unsigned short | uint16_t | short (주의) | int | 포트 번호, 카운터 |
| long | 32 | long (또는 int) | int32_t | int | int | 인덱스, IP주소, 상태 코드 |
| unsigned long | 32 | unsigned long | uint32_t | int (주의) | int | 식별자, 메모리 주소 |
| long long | 64 | long long | int64_t | long | int | 타임스탬프, UUID |
| unsigned long long | 64 | unsigned long long | uint64_t | long (주의) | int | 대용량 통계, 해시값 |
| float | 32 | float | float | float | float | 온도, 습도, 일반 센서 |
| double | 64 | double | double | double | float | GPS, 금융 데이터 |
| int8 (XTypes) | 8 | signed char | int8_t | byte | int | 소형 정수 제어값 |
| uint8 (XTypes) | 8 | unsigned char | uint8_t | byte | int | 비트마스크 구성 요소 |
(참조: 2의 데이터를 종합하여 재구성)
2. 문자열 타입 (Strings and Wide Strings): 텍스트 데이터의 관리
문자열은 가변 길이 데이터를 다루는 가장 기본적인 형태이지만, 실시간 시스템인 DDS에서는 메모리 관리의 불확실성을 내포하고 있어 신중한 사용이 요구된다. IDL은 8비트 문자열인 string과 와이드 문자열인 wstring을 제공하며, 각각 길이를 제한하는 Bounded 형태와 제한하지 않는 Unbounded 형태로 나뉜다.
2.1 Bounded vs Unbounded: 메모리 결정론의 문제
문자열 선언 시 최대 길이를 명시하느냐(Bounded) 그렇지 않으냐(Unbounded)는 단순한 문법적 차이를 넘어 시스템의 안정성과 성능에 지대한 영향을 미친다.1
2.1.1 ) Unbounded String (string)
- 구문:
string message; - 특징: 길이의 제한이 이론적으로 4GB(32비트 길이 필드)까지 가능하다. 개발자에게 최고의 유연성을 제공하지만, 수신 측 미들웨어는 데이터가 도착하기 전까지 얼마만큼의 메모리를 할당해야 할지 알 수 없다.
- 위험성: 이는 필연적으로 **동적 메모리 할당(Dynamic Memory Allocation)**을 유발한다. 실시간 시스템이나 임베디드 장치에서 잦은 동적 할당과 해제는 **메모리 단편화(Fragmentation)**를 초래하고, 최악의 경우 힙 메모리 고갈로 인한 시스템 셧다운으로 이어질 수 있다. 또한
malloc/free호출로 인한 예측 불가능한 지연(Latency Jitter)은 실시간성을 해치는 주범이다.9 - 매핑: C++에서는
std::string, Java에서는String객체로 매핑된다. C 언어 매핑에서는char*포인터로 관리되며, 사용자가 직접 메모리 수명 주기를 관리해야 하는 부담이 있다.2
2.1.2 ) Bounded String (string<N>)
- 구문:
string short_message; - 특징: 문자열의 최대 길이를
N으로 고정한다. - 이점: 시스템 설계 시 최악의 경우(Worst-case) 메모리 사용량을 예측할 수 있다. RTI DDS, Fast DDS 등 고성능 구현체들은 Bounded String에 대해 미리 할당된 연속 메모리 블록(Pre-allocated Buffer)을 사용하여 동적 할당 오버헤드를 제거하는 최적화를 수행한다.10
- 주의사항: 선언된 길이
N은 널 종료 문자(Null Terminator)를 제외한 순수 문자의 개수이다. C/C++ 레벨에서 버퍼를 할당할 때는 반드시N + 1바이트를 확보해야 함을 명심해야 한다. 예를 들어string은 11바이트의 메모리 공간을 필요로 한다.9
2.2 인코딩 전략: UTF-8의 부상
과거 IDL의 string은 Latin-1 인코딩을 주로 사용했으나, 글로벌 서비스 환경에서 다국어 지원은 필수가 되었다.
- String (UTF-8): DDS-XTypes 표준 이후,
string타입은 UTF-8 인코딩을 담는 표준 컨테이너로 자리 잡았다.10 UTF-8은 ASCII 문자에 대해서는 1바이트를 사용하고, 한글이나 한자 등에는 3바이트 이상을 사용하는 가변 길이 인코딩이다. Java나 C#과 같은 언어는 내부적으로 유니코드를 사용하므로, DDS 전송 시 미들웨어가 자동으로 UTF-8 변환을 수행한다. 반면, C++ 개발자는std::string에 담긴 데이터가 UTF-8임을 직접 보장해야 한다. - Wstring의 한계:
wstring은 플랫폼 간wchar_t크기 불일치(2바이트 vs 4바이트)로 인해 변환 비용이 발생하고 패킷 크기가 커지는 단점이 있다. 따라서 현대적인 DDS 시스템 설계에서는 **string타입에 UTF-8 텍스트를 담아 전송하는 것이 상호운용성과 대역폭 효율성 면에서 표준적인 패턴(Best Practice)**으로 권장된다.10
3. 구조체 (Struct): 데이터의 논리적 구조화
기본 타입들이 데이터의 원자라면, 구조체(Structure)는 이들을 결합하여 의미 있는 정보 단위(분자)를 만드는 틀이다. C/C++의 struct와 유사하지만, DDS IDL의 구조체는 데이터의 메모리 배치(Layout)와 상속(Inheritance), 확장성(Extensibility)에 대해 훨씬 엄격하고 풍부한 기능을 제공한다.
3.1 구조체 정의와 메모리 정렬 (Padding & Alignment)
구조체는 struct 키워드로 정의되며, 다양한 타입의 멤버를 포함할 수 있다.
struct Point {
long x;
long y;
};
struct Obstacle {
long id;
Point center; // 중첩 구조체
double radius;
boolean is_active;
};
위의 Obstacle 구조체가 네트워크로 전송될 때, 단순히 멤버들의 크기를 합친 것(4 + 8 + 8 + 1 = 21바이트)만큼 전송되는 것이 아니다. DDS의 직렬화 포맷인 CDR은 CPU의 데이터 접근 효율을 위해 메모리 정렬(Alignment) 규칙을 따른다.
- 정렬 규칙: 기본적으로 N바이트 크기의 기본 타입은 N바이트 경계(Boundary)에 정렬되어야 한다.
long(4바이트)은 4의 배수 주소에서 시작해야 하고,double(8바이트)은 8의 배수 주소에서 시작해야 한다. - 패딩(Padding): 정렬 규칙을 맞추기 위해 의미 없는 바이트(Padding Byte)가 멤버 사이에 삽입된다. 위 예제에서
id(4바이트) 뒤에center(8바이트)가 오는데,Point구조체의 첫 멤버x는 4바이트 정렬이 필요하므로 바로 붙을 수 있다. 하지만radius(8바이트)가 오기 전에center의 끝이 4바이트 경계라면, 8바이트 경계를 맞추기 위해 패딩이 삽입될 수 있다. - 설계 시사점: 구조체 멤버를 정의할 때, 크기가 큰 멤버부터 작은 멤버 순서로(예: double -> long -> short -> char) 배치하면 패딩으로 낭비되는 공간을 최소화할 수 있다. 이는 대규모 트래픽이 발생하는 시스템에서 의외로 큰 대역폭 절감 효과를 가져온다.12
3.2 구조체 상속 (Struct Inheritance): 데이터 모델의 확장
DDS-XTypes(Extensible and Dynamic Topic Types for DDS) 표준의 도입으로, IDL 구조체는 객체지향 언어의 클래스처럼 상속(Inheritance)을 지원하게 되었다.13
- 개념: 기존에 정의된 구조체(부모)를 확장하여 새로운 필드를 추가한 구조체(자식)를 정의한다. 이를 통해 데이터 모델의 계층적 설계와 재사용이 가능해진다.
- 문법: C++의 상속 문법과 유사하게 콜론(
:)을 사용한다.
// 부모 구조체: 일반적인 센서 데이터
struct SensorHeader {
unsigned long device_id;
long long timestamp;
};
// 자식 구조체: 온도 센서 (SensorHeader 상속)
struct TemperatureSensor : SensorHeader {
float celsius;
float fahrenheit;
};
// 자식 구조체: 위치 센서 (SensorHeader 상속)
struct PositionSensor : SensorHeader {
double latitude;
double longitude;
double altitude;
};
- 상호운용성 효과: 상속은 단순한 코드 재사용을 넘어 **‘유형 할당 가능성(Type Assignability)’**이라는 강력한 기능을 제공한다. 만약 수신 애플리케이션이
SensorHeader타입의 토픽을 구독하고 있다면, 송신 측이TemperatureSensor나PositionSensor타입의 데이터를 보내더라도, 수신 측은 이를SensorHeader로 간주하여 공통 필드(device_id,timestamp)를 정상적으로 수신하고 처리할 수 있다. 이는 시스템의 유연성을 극대화한다(단, 구조체가Appendable또는Mutable로 설정되어 있어야 함).6 - 제약 사항: 모든 DDS 구현체나 구버전 표준이 상속을 지원하는 것은 아니다. 레거시 시스템과의 연동이 필요하다면 상속보다는 구성(Composition, 멤버로 포함) 방식을 사용하는 것이 안전하다.14
3.3 중첩(Nested)과 재귀(Recursive) 구조체
구조체는 다른 구조체를 멤버로 포함(Nested)할 수 있으며, 이를 통해 복잡한 데이터 모델을 모듈화할 수 있다. 더 나아가, 트리(Tree)나 그래프(Graph)와 같은 자료구조를 표현하기 위해 자기 자신을 참조하는 재귀적 정의도 가능하다.16
- 전방 선언(Forward Declaration): 재귀 구조체를 정의하기 위해서는 컴파일러에게 “이런 이름의 구조체가 곧 나올 것이다“라고 알려주는 전방 선언이 필요하다.
- 제약: 전방 선언된 구조체는 그 크기를 알 수 없으므로, 직접적인 멤버 변수로 포함될 수 없고 반드시 **시퀀스(Sequence)**나 배열의 형태로만 포함되어야 한다. (포인터 개념과 유사함).
struct TreeNode; // 전방 선언
struct TreeNode {
long node_id;
string data;
sequence<TreeNode> children; // 재귀적 참조 가능 (시퀀스 사용)
// TreeNode direct_child; // 오류! 무한 크기가 됨
};
이러한 재귀적 구조체는 메뉴 시스템, 조직도, 경로 탐색 그래프 등을 DDS로 전송할 때 유용하게 사용된다.1
4. 시퀀스(Sequence)와 배열(Array): 집합 데이터의 처리
데이터 모델링에서 “여러 개의 데이터를 어떻게 담을 것인가“는 성능과 직결되는 문제이다. IDL은 이를 위해 배열(Array)과 시퀀스(Sequence)라는 두 가지 컬렉션 타입을 제공한다. 이 둘은 비슷해 보이지만, 메모리 할당 방식과 직렬화 동작에서 근본적인 차이를 보인다.17
4.1 배열 (Array): 정적 데이터 집합
배열은 선언 시점에 크기가 고정되는 데이터 타입이다.
- 구문:
타입 변수명[크기](예:long matrix;,octet mac_addr;).2 - 메모리 특성: 컴파일 시점에 크기가 결정되므로 스택(Stack) 메모리에 할당하기 용이하며, 런타임 오버헤드가 없다.
- 전송 특성: 가장 큰 특징이자 단점은 “항상 전체 크기만큼 전송된다“는 것이다.
long values을 선언하고 실제로는 5개의 값만 유효하더라도, 네트워크로는 100개 분량(400바이트)이 모두 전송된다. 나머지 95개는 쓰레기 값(Dummy Data)으로 채워진다. - 사용 권장: 데이터의 크기가 불변하는 경우(예: 물리적 좌표 행렬, MAC 주소, 고정 길이 해시 키)에만 사용해야 한다. 가변적인 데이터를 배열로 잡는 것은 대역폭 낭비의 지름길이다.
- 언어 매핑: C++11에서는
std::array로 매핑되어 STL 알고리즘과 호환되며, Java에서는 일반 배열(``)로 매핑된다.2
4.2 시퀀스 (Sequence): 동적 데이터 벡터
시퀀스는 가변 개수의 요소를 담을 수 있는 동적 배열로, C++의 std::vector나 Java의 ArrayList와 유사하다. DDS 데이터 모델링의 가장 강력한 도구 중 하나이다.18
- 구조: 시퀀스는 내부적으로 두 가지 메타데이터를 관리한다.
_length: 현재 유효한 요소의 개수._maximum: 담을 수 있는 최대 용량 (Capacity).
- 전송 특성: 시퀀스는 **“실제 유효한 데이터(
_length)만 전송”**한다. 최대 크기가 100이라도 현재 데이터가 5개라면,length=5라는 헤더 정보와 함께 5개의 데이터만 직렬화된다. 이는 가변 데이터를 다룰 때 배열 대비 압도적인 대역폭 효율을 제공한다.17
4.2.1 시퀀스의 종류: Bounded vs Unbounded
문자열과 마찬가지로 시퀀스도 경계 유무에 따라 나뉜다.
- Unbounded Sequence (
sequence<T>):
- 최대 크기 제한이 없다.
- 유연성은 좋으나, 데이터 수신 시마다 크기에 맞춰 메모리를 재할당(Reallocation)해야 하므로 메모리 파편화와 성능 저하를 유발할 수 있다.
- Bounded Sequence (
sequence<T, N>):
- 최대 크기를
N으로 제한한다. - 실시간 시스템 권장: 초기화 시점에
N만큼의 메모리를 미리 할당(Pre-allocation)해 두고, 런타임에는 데이터 복사만 수행함으로써 동적 할당 부하를 완전히 제거할 수 있다. 따라서 임베디드 시스템에서는 반드시 Bounded Sequence를 사용하는 것이 원칙이다.18
4.3 시퀀스 사용의 베스트 프랙티스
- 익명 시퀀스(Anonymous Sequence) 피하기:
구조체 멤버로 시퀀스를 직접 선언하는 것(익명 시퀀스)은 피해야 한다. 대신 typedef를 사용하여 시퀀스에 명시적인 이름을 부여해야 한다. 이는 코드 생성 시 타입 이름을 깔끔하게 만들고, 여러 구조체에서 동일한 시퀀스 타입을 재사용할 때 타입 호환성을 보장한다.1
// [나쁜 예] 익명 시퀀스 사용
struct Polygon {
sequence<Point> vertices; // C++ 코드 생성 시 이름이 복잡해질 수 있음
};
// [좋은 예] 명시적 타입 정의
typedef sequence<Point> PointSeq;
struct Polygon {
PointSeq vertices; // 깔끔하고 재사용 가능한 타입
};
- Zero-Copy와 Loan 메커니즘:
C++ 등 고성능 언어 바인딩에서는 시퀀스에 대해 Loan(대여) 메커니즘을 제공한다. 이는 미들웨어가 수신한 내부 버퍼의 포인터를 애플리케이션에 직접 빌려주는 방식으로, 사용자 공간으로의 데이터 복사(Memcpy) 비용을 제거한다. 대용량 영상 데이터나 고빈도 센서 데이터 처리 시 시퀀스의 Loan 기능을 활용하면 CPU 사용량을 획기적으로 줄일 수 있다.
4.4 배열 vs 시퀀스 비교 요약
| 특성 | 배열 (Array) | Bounded Sequence | Unbounded Sequence |
|---|---|---|---|
| IDL 구문 | T name[N] | sequence<T, N> | sequence<T> |
| 길이 | 고정 (항상 N) | 가변 (0 ~ N) | 가변 (0 ~ 무한) |
| 네트워크 전송량 | 항상 전체 크기 (N) 전송 | 실제 데이터 개수만 전송 | 실제 데이터 개수만 전송 |
| 메모리 관리 | 스택/정적 할당 용이 | 사전 할당(Pre-alloc) 가능 | 동적 할당 필수 (파편화 위험) |
| 주요 용도 | 좌표, MAC 주소, 고정 해시 | 센서 샘플 목록, 로그 메시지 | 파일 전송, 가변 히스토리 |
| 권장 환경 | 초소형 임베디드, 고정 데이터 | 실시간 제어 시스템, IoT | 엔터프라이즈, 비실시간 시스템 |
(참조: 2의 분석 종합)
5. 결론 및 다음 단계
본 절에서는 DDS 데이터 모델링의 근간이 되는 기본 타입, 구조체, 그리고 시퀀스에 대해 상세히 살펴보았다. 올바른 타입의 선택은 단순히 문법을 맞추는 것이 아니라, 시스템의 대역폭 효율성, 메모리 안정성, 그리고 이기종 간의 상호운용성을 설계하는 과정이다.
- 수치 데이터는 범위에 맞는 정수형을 선택하되, 플랫폼 독립성을 위해 IDL 기본 타입의 비트 크기를 항상 의식해야 한다.
- 바이너리 데이터는 반드시 **
octet**을 사용하여 인코딩 왜곡을 방지해야 한다. - 문자열과 시퀀스는 가급적 Bounded 형태를 사용하여 메모리 예측 가능성을 확보해야 한다.
- 구조체는 데이터의 논리적 단위를 정의하며, 상속을 통해 확장 가능한 모델을 구축할 수 있다.
이제 데이터를 정의하는 그릇은 준비되었다. 하지만 수많은 데이터가 고속으로 오가는 분산 환경에서 특정 데이터 객체(예: ’1번 모터’의 온도 vs ’2번 모터’의 온도)를 식별하고 관리하는 방법이 필요하다. 이어지는 “3.3 키(Key) 설정을 통한 데이터 인스턴스(Instance) 구분 전략“에서는 구조체의 특정 멤버를 ’키(Key)’로 지정하여, 단순한 메시징을 넘어 **데이터 중심(Data-Centric)**의 상태 관리를 실현하는 DDS의 핵심 기능에 대해 다룰 것이다.
6. 참고 자료
- Interface Definition Language - Object Management Group (OMG), https://www.omg.org/spec/IDL/4.2/PDF
-
- Defining a data type via IDL - 3.4.1 - eProsima Fast DDS, https://fast-dds.docs.eprosima.com/en/3.4.x/fastddsgen/dataTypes/dataTypes.html
- Extensible and Dynamic Topic Types for DDS - Object Management Group (OMG), https://www.omg.org/spec/DDS-XTypes/1.1/PDF
- Translations for IDL Types - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/users_manual/users_manual/Translations_for_IDL_Types.htm
- IDL - Interface Definition and Language Mapping - ROS2 Design, https://design.ros2.org/articles/idl_interface_definition.html
- What syntax and datatypes are supported in IDL - Eclipse Cyclone DDS - FAQ, https://cyclonedds.io/content/guides/supported-idl.html
- Mapping of OMG IDL Statements to C++, https://docs.oracle.com/cd/E15261_01/tuxedo/docs11gr1/CORBA_ref/member.html
- 10 Introduction to OMG IDL, https://mhanckow.students.wmi.amu.edu.pl/corba/IDL.html
- CoreDX DDS Type System - Twin Oaks Computing, Inc, https://www.twinoakscomputing.com/documents/CoreDX_DDS_XTypes_2017_01_26.pdf
- 20.1.2 Strings and Wide Strings - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/users_manual/users_manual/Strings_and_Wide_Strings.htm
- std::wstring VS std::string - c++ - Stack Overflow, https://stackoverflow.com/questions/402283/stdwstring-vs-stdstring
- Mapping of OMG IDL Statements to C++, https://docs.oracle.com/cd/E13203_01/tuxedo/tux81/CORBA_ref/member.htm
- How to model in idl for DDS - Stack Overflow, https://stackoverflow.com/questions/15337914/how-to-model-in-idl-for-dds
- Structure Inheritance - RTI Community, https://community.rti.com/static/documentation/connext-dds/current/doc/manuals/connext_dds_professional/extensible_types_guide/extensible_types/StructureInheritance.htm
- Auto generated code for Inherited structure type IDL doesn’t seem to work · Issue #12 · eProsima/Fast-DDS-Gen - GitHub, https://github.com/eProsima/Fast-DDS/issues/925
- How to declare/use forward reference to struct in CORBA IDL? - Stack Overflow, https://stackoverflow.com/questions/17256682/how-to-declare-use-forward-reference-to-struct-in-corba-idl
- Difference between array and sequence | Data Distribution Service (DDS) Community RTI Connext Users, https://community.rti.com/forum-topic/difference-between-array-and-sequence
- 4.13. Working With Sequences — RTI Connext DDS Micro 2.4.14.0 documentation, https://community.rti.com/static/documentation/connext-micro/2.4.14/doc/html/usersmanual/sequences.html
- Unbounded arrays and sequences | Data Distribution Service (DDS) Community RTI Connext Users, https://community.rti.com/forum-topic/unbounded-arrays-and-sequences