본문으로 건너뛰기

원소가 없을 수 있는 배열 — T?[] · T[]? · T?[]?

문서 목록으로

상태: 구현됨 — 표기·모델·검증, json, 형식 v106, 그리고 모든 언어의 리더와 생성기까지

배열의 원소 하나가 값을 갖지 않는 것을 시트가 적고 파일이 담을 수 있게 하는 설계입니다.

지금은 배열 전체만 옵셔널입니다. 옵셔널 필드?를 배열 괄호 뒤에만 두기로 정하였고, 그때의 근거는 「한 셀에 든 목록의 원소는 다 있거나 다 없거나」였습니다. 그 전제가 실제 시트에서 성립하지 않는 자리가 셋 있고, 이 문서는 그 셋을 하나의 표기로 해소합니다.


1. 문제 — 원소의 없음을 적을 자리

자리지금 일어나는 일
가운데가 빈 연번 배열Slot1·Slot3만 채운 로우는 거부됩니다. 가운데 원소가 타입의 빈 값이 되고, 읽는 쪽이 「없음」과 「0」을 구별할 수 없기 때문입니다. 완화하려면 AllowArrayGaps구별을 포기하는 수밖에 없습니다
엇갈린 ?Layer[0]은 필수, Layer[1]은 옵셔널로 적은 시트에서 첫 컬럼이 전부를 대표합니다. 원소마다 다르게 적을 방법이 없어서 내린 결정입니다
구분자 셀의 -10;-;30은 오류입니다 (빈 칸과 없음). 원소가 없음을 가질 수 없으므로 그 자리에서 거부합니다

셋 다 같은 빈칸을 다르게 우회한 것입니다. 원소의 없음을 표현할 수 있으면 셋이 함께 없어집니다.

2. 표기 — ?의 자리와 그 뜻

타입 칸배열 자체원소
int[]필수필수
int[]?없을 수 있습니다필수
int?[]필수없을 수 있습니다
int?[]?없을 수 있습니다없을 수 있습니다

C#의 읽기와 같습니다. int?[]는 원소가 nullable인 배열이고, int[]?는 배열 변수가 nullable이며, int?[]?는 둘 다입니다. 생성 코드를 받는 언어들이 ?로 뜻하는 것과 같게 읽히는 것이 ?를 타입에 붙인 이유였고, 원소 쪽도 같은 이유로 같은 자리에 붙입니다.

괄호 앞의 ?는 원소, 뒤의 ?는 배열입니다. 파서는 이름을 뒤에서부터 읽습니다 — 끝의 ?를 떼면 배열의 답이고, 남은 int?[]에서 괄호를 떼고 다시 끝의 ?를 떼면 원소의 답입니다. ?int[]처럼 앞에 붙인 것은 거부하고, 그 자리에서 두 표기를 함께 냅니다.

required가 계속 기본입니다. ?를 적지 않은 컬럼은 지금과 완전히 같으므로, 이 기능은 존재하는 모든 시트에 대해 더해지기만 합니다.

3. 셀에 적는 법 — 없음은 여기서도 -

원소의 없음도 -입니다. 빈 칸과 없음이 정한 것을 원소 자리에 그대로 적용합니다 — 표기가 자리마다 다르면 외울 것이 하나 더 생깁니다.

배열 셀int[]int?[]
10;20;30[10, 20, 30][10, 20, 30]
10;-;30오류[10, null, 30]
10;;30오류int는 빈 칸을 읽지 못합니다오류 — 같습니다
-오류int[]?가 아닙니다배열 전체의 없음. int?[]?일 때만
(빈 칸)빈 배열빈 배열

빈 원소는 없음이 아닙니다. ?가 붙어도 그렇습니다. 셀 문법에서 빈 칸이 타입의 빈 값이듯, 빈 원소도 그 타입이 빈 칸을 읽는 방법 그대로입니다 — string[]a;;b는 가운데가 빈 문자열이고, 그것은 값입니다.

실측이 이것을 요구합니다. 이전 대규모 코퍼스의 산출물에는 빈 문자열 원소를 가진 배열이 5,200개 있습니다. 빈 원소를 없음으로 읽거나 오류로 만들면 그 5,200개가 전부 다른 것이 되고, 그 변화는 조용합니다. 없음은 적은 것이어야 합니다.

연번 컬럼 그룹 — 컬럼의 ?는 원소의 답

컬럼 여러 개가 배열 하나로 접히는 그룹(Tag1·Tag2·Tag3)에는 배열을 가리키는 타입 칸이 없습니다. 칸마다 적혀 있는 것은 원소 하나의 타입이고, 배열은 접으면서 생깁니다. 그래서 T?[]를 적을 자리가 없고, 대신 그 칸의 ?가 그 원소의 답입니다.

Tag1Tag2Tag3stringstring?
abc["a","b","c"]["a","b","c"]
a-c오류 — 원소가 없음을 가질 수 없습니다["a", null, "c"]
-bc오류[null, "b", "c"]
a(빈 칸)c["a","","c"]["a","","c"]

배열 전체의 없음은 연번 그룹에 없습니다. 그 컬럼들은 모든 로우에 존재하므로 「배열이 없다」가 뜻할 수 있는 것은 「원소가 전부 없다」뿐이고, 그것은 위 표가 원소마다 적습니다. 자르기를 켠 테이블에서 그 로우는 길이 0, 즉 []입니다. 정말 「이 배열 자체가 없을 수 있다」가 필요하면 구분자 배열(T[]?)이 그것입니다 — 거기서는 셀 하나가 배열 하나에 대응합니다.

그 읽기가 하고 있던 일

첫 컬럼의 ?를 배열 전체의 것으로 읽는 동안, 배열의 존재 여부를 원소 0의 셀이 정하고 있었습니다. JSON과 presence 비트맵이 같은 자리를 봅니다.

// JsonExporter — 접힌 배열
sf.Fields[0].IsRequired || row[sf.FirstField!.Index].HasValue ?: null

// BinaryExporter — presence 비트맵
if (rows[at][column.TagCarrier.Index].HasValue) // TagCarrier == Cells[0]

그래서 Tag1이 없고 Tag2에 값이 있는 로우는 배열 전체가 null이 되고 그 값이 사라졌습니다. 원소 0이 비었다는 것은 원소 1~N에 대해 아무 말도 하지 않으므로, 이 대리는 그 자체로 틀린 것이었습니다. 위 표의 세 번째 줄이 그것을 고정합니다.

타입은 첫 컬럼이 계속 대표합니다. 배열 옵셔널리티의 규칙은 그대로입니다 — 바뀌는 것은 그 표시가 무엇을 정하는가이고, 어느 컬럼이 정하는가가 아닙니다.

4. 가운데 구멍 — 거부에서 표기로

가변 길이 레코드 배열 3절이 가운데 빈칸을 거부하는 이유는 「없음을 표현할 수 없어서」였습니다. 그 이유가 없어지므로 판정이 이렇게 바뀝니다.

원소 타입가운데가 -가운데가 빈 칸
T[] (원소 필수)오류 — 원소는 없음을 가질 수 없습니다지금 그대로. 값을 읽을 수 있으면 값이고, 아니면 오류
T?[]통과 — 그 원소가 없는 것입니다같습니다. 없음이 아니라 값입니다

AllowArrayGaps는 남습니다. 뜻이 좁아집니다 — 원소가 필수인 배열에서 빈 자리를 통과시키는 설정이고, 지금과 같이 기본은 끔입니다. 고칠 수 있는 시트라면 컬럼을 T?[]로 적고 그 셀에 -를 적는 것이 답이고, 그 답은 데이터에도 남습니다. AllowArrayGaps의 역할은 여전히 「구별을 포기한다」이므로 남의 워크북을 읽는 자리의 것입니다.

원소가 옵셔널인 그룹은 이 검사에서 빠집니다. 시트가 「원소가 없을 수 있다」고 적은 자리에서 구멍은 실수가 아니라 그 시트가 적은 것이기 때문입니다. AllowArrayGaps가 남아 있는 자리는 원소가 필수인 배열이고, 거기서 구멍은 여전히 아무도 요청하지 않은 빈 값입니다.

5. 와이어 — 비트 7과 원소 비트맵

형식의 정본은 TCB v106입니다. 바이트 배치와 실제 파일 하나의 바이트 walk, 그리고 105 파일을 거부하는 이유가 거기 있습니다. 이 절은 그 결정의 근거만 적습니다.

와이어 바이트의 비트 7을 원소 nullability로 씁니다. v103이 비트 6을 쓰면서 예약해 둔 마지막 비트입니다.

7 6 5 4 3 2 1 0
[ ?[] ] [?] [kind] [ element ]

이 문서가 쓰기 시작하는 비트
마스크의미
0x0F엘리먼트 타입
0x30kind (스칼라 · 고정 배열 · 가변 배열)
0x40배열(또는 스칼라) 자체가 없을 수 있음 — 로우당 1비트의 presence 비트맵
0x80원소가 없을 수 있음 — 원소당 1비트의 비트맵

두 비트는 직교합니다. int?[]?는 둘 다 켜고 비트맵을 둘 갖습니다. kind 값을 쓰지 않는 이유는 v103과 같습니다 — 원소 nullability는 kind와 직교하고, 남은 kind 값 하나는 진짜 새로운 형태를 위한 것입니다.

블록 배치

블록: [비트 6이면 로우 presence 비트맵: 로우당 1비트]
[비트 7이면 원소 presence 비트맵: 기록된 원소당 1비트]
encoding이 정한 배치로 담긴 값들
  • 원소 비트맵의 길이는 실제로 기록된 원소의 총수입니다. 고정 배열이면 rowCount × count, 가변 배열이면 로우 길이의 합입니다. 리더는 이미 길이를 순서대로 읽으므로, 누적 카운터 하나가 늘어납니다.
  • 값은 모든 원소에 대해 기록합니다. 없는 원소는 타입의 빈 값을 차지합니다. v103이 로우에 대해 내린 것과 같은 결정이고, 같은 이유로 인코딩 경로가 하나도 바뀌지 않습니다 — 배열은 길이 스트림과 원소 스트림을 따로 인코딩하는데(v104), 그 둘 중 어느 것도 비트맵을 보지 않습니다.
  • 비트맵은 RAW입니다. v103의 로우 비트맵과 같습니다.
  • byteLength는 두 비트맵을 포함한 블록의 총 바이트입니다. 모르는 컬럼 건너뛰기가 advance(byteLength) 한 번이라는 불변식이 유지됩니다.

형식 버전을 106으로 올립니다. 비트 7을 검사하지 않는 기존 리더는 원소 비트맵을 값으로 읽으므로, 감지되지 않는 읽기 오류보다 버전 거부가 낫습니다. 원소 옵셔널 컬럼이 없는 파일은 버전 4바이트만 달라집니다.

6. 액세서와 JSON

HasCostsAt(i)를 냅니다. 값의 타입은 그대로 둡니다.

var r = GameData.Drop.FindByIndex(2);
r.Costs; // [10, 0, 30] - 없는 원소는 타입의 빈 값
r.HasCostsAt(1); // false - 시트가 `-`라고 적었습니다
r.HasCosts; // 배열 자체의 존재. `int?[]?`에만 있습니다

옵셔널 필드HasX를 고른 이유가 그대로 적용됩니다 — 13개 언어에서 같은 형태인 것이 이것뿐이고, 언리얼의 UPROPERTYTOptional을 담지 못하며, 읽는 쪽 코드가 짧아지지도 않습니다. 이름은 생성 코드의 이름 체계를 따릅니다.

JSON은 null입니다[10, null, 30]. 배열 자체가 없으면 지금처럼 null이고, 둘이 겹치면 null[…, null, …]이 각자의 자리에 나옵니다.

7. 모델

무엇어디
원소가 필수인가Field.ElementsRequired. 기본 true. Field.IsRequired가 배열 자체를 정하는 것과 같은 자리
어느 원소가 없는가Cell의 원소별 presence. 값 배열과 길이가 같은 bool[]이고, 원소가 필수인 컬럼에서는 null입니다

Cell.HasValue는 배열 자체의 것으로 남습니다. 원소의 없음과 배열의 없음은 서로 다른 사실이고, 하나로 뭉치면 「원소가 전부 없는 배열」과 「배열이 없음」이 같아집니다.

연번 컬럼 그룹은 셀마다 이미 HasValue를 갖습니다. 그룹의 원소별 presence는 그 값들을 모은 것이므로, 새로 담을 것이 없습니다 — 컬럼이 곧 원소이기 때문입니다.

8. 범위 밖

레코드의 멤버. 레코드 배열이 표현하는 없음은 원소 개수이고, 그것은 비트맵이 아니라 길이입니다. 「Id는 있는데 Count는 없다」는 레코드가 소비하는 쪽에 하나의 값이라는 결정과 충돌하므로, 이 문서는 스칼라 배열에 한정합니다.

데이터베이스·html·summary·history. 옵셔널 컬럼을 이미 이름과 함께 거부하고 있고, 같은 자리에서 같은 이유로 거부합니다.

9. 비용과 순서

이 기능의 비용은 모든 언어의 리더입니다. v103이 비트 6과 로우 비트맵을 심을 때 지났던 경로와 같습니다.

단계무엇
1표기·모델·검증 — 코어에서 T?[]를 읽고, - 원소를 받고, 4절의 판정을 옮깁니다 —
2json 익스포터 — 여기까지가 「셀에서 소비까지」의 첫 왕복입니다 —
3형식 v106과 binary 익스포터 — 비트 7과 원소 비트맵 —
4모든 언어의 리더와 생성기 — 각 언어의 per-element 답과 비트맵 읽기 —
5골든 재기록 · 전 언어 비교본 · 샘플 재생성 —

2단계에서 한 번 멈출 수 있습니다. json까지 오면 시트가 무엇을 적을 수 있고 소비하는 쪽이 무엇을 받는지가 확정되고, 형식과 모든 언어는 그 확정 위에서 기계적으로 따라옵니다. v103이 그렇게 진행되었고, 그 순서가 형식을 두 번 고치는 일을 방지하였습니다.

1·2단계에서 실제로 한 것

무엇어디
?를 양쪽에서 읽기CookingContext.SplitOptionalMarkers. 자리를 틀리게 적으면 두 표기를 함께 냅니다
모델Field.ElementsRequired · Cell.ElementHasValue
원소의 -ParseArrayValue가 받고, 원소가 필수면 거부하며 고치는 방법 3개를 냅니다
검증없는 원소는 범위·허용값 검사를 건너뜁니다 — 값이 아니라 빈 값이기 때문입니다
json없는 원소가 null
나머지 타깃ITarget.SupportsOptionalElements. 기본 false이고, 만나면 타깃과 컬럼을 이름으로 거부합니다

거부가 이 단계를 안전하게 만듭니다. 아직 읽지 못하는 것을 읽는 척 내보내는 대신, 그 타깃을 recipe에서 빼거나 ?를 괄호 밖으로 옮기라고 보고합니다. 모든 언어가 로우 비트맵을 배우는 동안 SupportsOptionalFields가 하던 것과 같습니다.

3단계에서 한 것 — 형식 v106

무엇어디
비트 7TcbFormat.WireElementNullable. Wire()가 두 비트를 함께 쓰고, ElementNullableOf()가 읽습니다
원소 비트맵BinaryExporter.WithPresence가 로우 비트맵 , 값 에 붙입니다. 로우 비트맵과 같이 자기 인코딩 바이트를 답니다
비트맵의 길이ElementPresenceBits — 값 writer가 지나가는 세 갈래를 그대로 지나가며, 실제로 기록된 원소만큼 비트를 냅니다
버전105 → 106. 모든 런타임의 상수가 함께 올라갔고, 그래서 .tcb 골든 전부와 샘플이 4바이트씩 바뀌었습니다
바이트 게이트The_file_declares_the_two_bitmaps_separately — 파일에서 디스크립터를 직접 읽어 비트 6·7이 따로 켜지는 것을 확인합니다

4단계에서 한 것 — 모든 언어

무엇어디
비트 7 읽기모든 런타임의 컬럼 디스크립터에 elementNullable 한 칸
비트맵 읽기ReadElementPresence — 길이(counter32)와 인코딩 바이트를 읽고 비트맵을 냅니다. 행 비트맵과 같은 자리, 같은 형태
스키마 불일치CheckColumn이 행 비트맵에 대해 하던 말을 원소 비트맵에도 합니다. 기대하지 않은 비트맵을 값으로 읽는 것을 막는 자리입니다
per-element 답언어마다 그 언어의 관용구로 — C#·TypeScript는 HasXAt(i), C++는 has_x_at(i), 나머지는 값 옆의 bool 배열. 행 단위 답이 각 언어에서 이미 취한 형태와 같게 두었습니다
걷는 방법모든 언어가 같습니다 — 카운터 하나가 모든 로우의 모든 원소마다 한 번 증가합니다. 로우마다 증가하는 리더는 자기 안에서는 일관되므로, 이것이 언어별로 틀릴 수 있는 유일한 자리입니다

게이트. C#과 TypeScript는 파일을 읽어 JSON과 대조하고(cs-check-nullable-elements · ts-check-nullable-elements), Python·Ruby·PHP는 읽어서 값과 존재 여부를 직접 확인하며, 나머지는 컴파일 게이트에 이 시나리오가 추가되었습니다. 언리얼은 UHT가 헤더를 받는지까지 봅니다.

9.1 참조 배열이 비트맵을 읽고 버립니다 — 2026-08-26

생성된 C++에서 var_array_ref 는 원소 비트맵을 읽기만 하고 적용하지 않습니다. 읽는 것은 그 블록을 지나가기 위해 필요하고, 지나간 뒤에 무엇이 적혀 있었는지는 쓰지 않습니다.

찾은 경위가 이 항목의 값입니다. GCC 의 -Wunused-but-set-variable 이 검출했습니다 — 비트맵을 걷는 커서가 선언되고 초기화만 되고 한 번도 증가하지 않는 것이 그 경고이고, serial-ref 픽스처의 TrimKit 이 그 형태였습니다. CI 가 리눅스에서 처음 돌아간 날 드러났습니다. MSVC 는 같은 자리를 경고하지 않습니다.

지금은 커서를 그 형태에서 아예 내지 않게 했습니다. 컴파일이 통과하고 값은 변하지 않습니다 — 골든에서 움직인 것은 그 세 줄뿐입니다.

남은 물음은 그 형태에 원소 옵셔널이 있어도 되는가입니다. 셋 중 하나입니다.

가능성그러면
참조 배열에 원소 옵셔널이 성립하지 않는다쿠킹에서 거부해야 합니다. 지금은 조용히 지나갑니다
성립하는데 C++ 만 빠뜨렸다리더 사이의 값 차이입니다. 다른 언어를 확인해야 합니다
성립하고 비트맵이 참조 해석에서 이미 반영된다문서에 그렇게 적어야 합니다

적합성 코퍼스에 이 조합이 없습니다. 있었다면 리더 대조가 답을 이미 주었을 것이고, 없다는 사실 자체가 이 물음이 열려 있는 이유입니다.

10. 채택하지 않은 안

센티넬 값. 없는 원소를 「그 타입이 쓰지 않는 값」으로 적는 안입니다. stringbool에는 그런 값이 없고, int에서 고르면 그 값을 쓰는 시트가 표현할 수 없는 값을 갖게 됩니다. v103이 로우 presence에서 같은 이유로 거부한 안입니다.

kind 값 추가. kind는 2비트이고 남은 값이 하나뿐입니다. 원소 nullability는 kind·배열 nullability와 직교하므로, 한 자리를 쓰고도 조합을 표현하지 못합니다.

언어별 nullable 원소 타입 — C#의 int?[], TypeScript의 (number | null)[], Rust의 Vec<Option<i32>>. 언어마다 다른 표현을 쓰면 언어별 타입 매핑표가 하나 더 생기고, 그 표가 틀려도 아무 신호가 없습니다. 언리얼에서는 애초에 불가능합니다.

빈 원소를 없음으로 읽기. 표기가 하나 줄어드는 대신 3절의 5,200개가 조용히 뜻을 바꿉니다. 빈 칸과 없음이 셀에서 내린 결정과도 어긋납니다.

11. 검증

무엇어떻게
표기 4종int[] · int[]? · int?[] · int?[]?를 한 픽스처에. 각각 값·-·빈 원소 로우
- 원소T?[]에서 통과, T[]에서 거부. 거부 메시지가 T?[]를 안내하는지
가운데 구멍연번 컬럼 그룹에서 -가 통과하고 빈 칸이 4절대로 판정되는지
배열과 원소의 조합int?[]?에서 「배열 없음」과 「원소 없음」이 JSON에서 각자의 자리에 나오는지
와이어비트 7이 켜진 컬럼의 바이트를 파일에서 직접 읽어 확인 — 3단계에서 함
모든 언어HasXAt(i)의 값이 JSON과 일치하는지를 라운드트립으로. string?[]을 반드시 포함합니다 — 값만 보면 「빈 문자열」과 「없음」이 같아 보이므로, 비트맵이 틀려도 값 비교로는 드러나지 않습니다

1·2단계의 게이트는 NullableArrayElementTests이고, 픽스처는 nullable-elements 하나에 표기 4종과 string?[]을 함께 담았습니다. no-value-element는 원소가 필수인 배열의 거부를, nullable-elements-binary는 아직 담지 못하는 타깃의 거부를 봅니다.

12. 딸린 정리 — 생성 코드의 고정 길이

결정: 컬럼 하나가 배열 하나를 갖는 형태에서는 길이를 생성 코드에 적지 않습니다. Tag1·Tag2를 접은 스칼라 배열이 그런 형태입니다. 한 로우가 원소를 몇 개 갖는지는 파일의 컬럼 디스크립터에 있고, 읽기가 그것을 따릅니다.

이 문서의 일부인 이유는 원소 비트맵을 넣으면서 같은 자리를 두 번 고치게 되었기 때문입니다 — 원소 개수는 비트맵을 걷는 데도 필요한 수이고, 그 수의 출처가 생성 시점의 컬럼 수와 파일의 디스크립터 두 곳이면 둘이 어긋나는 경우가 생깁니다.

없앤 것과 남긴 것

형태길이의 출처생성 코드
스칼라 배열 (Tag1·Tag2 접기)컬럼 디스크립터의 count상수 없음. 읽기가 파일의 수로 할당하고 순회합니다
구분자 배열 (T[])로우마다 기록된 길이이전과 같습니다 — 애초에 상수가 없었습니다
레코드 그룹 (Slot1.Id·Slot2.Id)컬럼들이 합의한 수공개하지 않는 상수. 컬럼 여럿이 배열 하나를 채우므로 어느 컬럼도 그것을 혼자 갖지 않습니다
배열의 배열 (Grid1.1·Grid1.2)같음 — 바깥은 컬럼 수, 안쪽은 컬럼이 적은 수같음
compact-row JSON 읽기위치숫자가 그대로 남습니다. 위치로 읽는 형식이므로 길이가 곧 자리 수입니다

판정 기준 하나입니다 — 그 배열을 컬럼 하나가 혼자 갖는가. 혼자 가지면 길이는 데이터이고, 여럿이 나눠 채우면 길이는 생성된 형태의 일부입니다. 후자에서 컬럼 수가 달라지는 것은 데이터의 변화가 아니라 스키마의 변화이므로, 리더가 거부하는 것이 맞습니다.

공개 API에서 없어진 것

C#과 TypeScript만 길이를 공개하고 있었습니다(Slot_N · Slot_MAX_N · Grid_M). 둘 다 없어졌습니다 — 여기 적히는 수는 이 코드를 생성한 시점의 시트가 가졌던 것이고, 소비하는 쪽이 그것을 붙들면 데이터가 더 이상 동의하지 않아도 되는 수를 붙드는 것이 됩니다. 배열의 길이(Length · length · len())가 답이고, 그것은 낡지 않습니다.

C의 선언 — 포인터와 개수

다른 언어의 배열은 이미 길이를 자기 안에 들고 있는 자료구조입니다 — T[] · Vec<T> · std::vector<T> · List<T>. C에는 그런 것이 없고, 고정 배열을 구조체 안에 박아 두면 그 크기가 구조체의 크기가 되므로 데이터에서 정할 수 없습니다. 선택은 그 숫자와 포인터 중 하나이고, 길이가 파일의 것이므로 포인터입니다.

/* 전 */
const char* tag_array[2];

/* 후 */
const char** tag_array;
int32_t tag_array_count;

이미 있던 형태입니다. TrimTrailingArrayElements를 켠 테이블과 구분자 배열이 v100부터 그렇게 나오고 있었으므로, 한 테이블을 받아 쓰는 코드는 두 형태를 이미 함께 다룹니다. 파일이 그 컬럼을 담고 있지 않으면 포인터는 NULL이고 개수는 0입니다 — 개수를 보고 도는 루프는 그대로이고, 개수를 보지 않고 인덱싱하는 코드만 문제가 됩니다.

리더에서 바뀐 것

모든 런타임의 CheckColumn음수 count를 「주장하지 않음」으로 받습니다. 종류(kind)는 여전히 멤버의 주장이므로 대조하고, 길이만 대조하지 않습니다. 시트에 Tag3이 하나 늘어난 파일을 이전 코드로 읽으면 지금까지는 거부였고, 지금은 원소 3개를 읽습니다.

그리고 디스크립터의 count가 검사 대상이 되었습니다. 읽기가 이 수로 할당하게 되었으므로, 행 수에 대해 이미 하던 검사를 원소 수에도 합니다 — RAW 블록은 원소 하나가 최소 1바이트이므로 countbyteLength보다 클 수 없습니다. 어긋나면 아무것도 할당하기 전에 헤더 단계에서 멈춥니다. 인코딩된 블록에는 그 하한이 없고(런 하나가 원소 여럿을 덮습니다), 거기서는 읽기 자체가 바이트를 다 쓰는 순간 거부하는 것이 방어선입니다 — 가변 배열의 길이가 v100부터 그랬던 것과 같은 자리입니다.

로우가 없는 테이블은 이 검사에 걸리지 않습니다. 컬럼의 count를 바이트 없는 블록과 함께 적는 것이 올바른 파일이기 때문입니다.

그래서 이 변경은 파일 형식을 건드리지 않습니다. 디스크립터가 v100부터 담고 있던 수를 읽기가 비로소 쓰는 것뿐입니다.

게이트

무엇어디
생성 코드에 길이가 없는지ArrayTypeTests — 내보낸 페이지에서 -1column.Count를 찾고, SlotArray_N이 없는 것을 확인합니다
런타임이 음수를 받는지FixedArrayLengthTests — 손으로 만든 파일에 원소 3개를 담고 길이를 주장하지 않는 멤버로 읽습니다. 시트로는 만들 수 없는 경우입니다
주장하는 멤버는 여전히 거부하는지같은 파일을 길이 2를 주장하며 읽어 거부를 확인합니다 — 레코드 그룹의 경우입니다
count 검사블록이 담을 수 없는 수를 적은 파일이 헤더에서 멈추는지, 그리고 빈 테이블이 통과하는지
모든 언어각 언어의 NestedAndOptional 게이트가 컴파일하고, 적합성 코퍼스가 자기 세대의 파일을 왕복합니다
참조의 배열serial-ref 픽스처와 SerialReferenceTests. 아래 참조

딸려 나온 것 — 참조의 배열

foreign[]의 거부 메시지가 안내하는 형태(Slot1·Slot2를 접은 것)이 어느 픽스처에도 없었습니다. 모든 언어가 그 코드를 생성하고 있었고 아무도 읽지 않았으므로, 길이가 페이지에서 파일로 옮겨가는 이번 변경이 그 자리를 세 번 건드렸습니다.

무엇어디
키 배열의 할당C#. 선언이 상수 크기 할당에서 빈 배열로 바뀌었으므로 읽기가 파일의 수로 할당합니다. 이것 없이는 첫 로우에서 범위를 벗어납니다. 당시에는 해석 여부 플래그의 배열도 함께였습니다 — 그 플래그는 이후 없앴습니다
해석 루프의 상한C#·TypeScript. 시트의 컬럼 수를 적고 있었습니다 — 원소가 하나 늘면 마지막이 해석되지 않고, 하나 줄면 범위를 벗어납니다. 나머지 언어는 키 배열의 길이를 쓰고 있었습니다
해석되지 않은 원소의 자리TypeScript. 배열을 비워 두고 해석된 원소만 넣고 있어서, 두 번째 참조가 아무것도 가리키지 않는 로우의 배열은 길이가 1이었습니다 — for ... of가 구멍을 표 없이 지나갑니다. 컬럼이 적은 길이만큼 만듭니다

픽스처는 참조의 두 형태를 함께 담습니다. 대상이 로우 전체인 것과 그 로우의 값 하나인 것은 해석 결과의 타입이 다르므로, 생성된 페이지가 둘을 뒤바꾸면 컴파일되지 않거나 엉뚱한 배열에 씁니다.