C 언어에서 문자열의 끝을 알리는 데 사용되는 NUL 종료 문자열 방식이 지난 50년간 수많은 프로그래밍 오류와 비효율의 원인이었다는 비판이 제기되었습니다. 이 방식은 문자열의 실제 길이를 별도로 저장하지 않고, 문자열의 끝에 NUL 바이트(null byte)를 넣어 경계를 표시합니다. 1970년대 메모리와 성능 제약이 심했던 시절에는 합리적이었을 수 있지만, 현대에는 이러한 방식을 고수할 이유가 거의 없다는 지적입니다.
NUL 종료 방식은 문자열 길이를 알기 위해 매번 처음부터 끝까지 스캔해야 하는 `strlen` 함수 호출을 반복하게 만듭니다. 이는 불필요한 연산과 복잡성을 증가시키며, `snprintf`, `sizeof`, `strlen` 등 함수마다 NUL 바이트 포함 여부에 대한 규칙이 달라 개발자에게 혼란을 줍니다. 예를 들어 `sizeof("string")`은 NUL 바이트를 포함하지만 `strlen("string")`은 제외합니다. 또한, 중간에 NUL 바이트가 포함된 바이너리 데이터를 안전하게 처리하기 어렵고, 부분 문자열(substring)을 만들 때마다 새로운 메모리 할당과 복사가 발생하여 비효율적입니다. 이로 인해 C 언어에서 발생하는 메모리 문제와 오버플로 버그의 상당수가 이 설계에서 비롯되었다는 견해가 많습니다.
대안으로 제시되는 것은 포인터와 문자열의 길이를 함께 저장하는 '길이 기반 문자열(length-based string)' 방식입니다. 이는 `struct String { u8* data; u64 size; };`와 같은 구조체 형태로 구현되며, 문자열의 길이를 명시적으로 알 수 있어 `strlen` 같은 반복적인 탐색이 필요 없습니다. `size == 0`으로 빈 문자열을 일관되게 처리할 수 있고, NUL 바이트를 포함한 임의의 바이너리 데이터도 안전하게 저장 및 처리할 수 있습니다. 또한, 원본 메모리의 일부를 가리키는 부분 문자열을 할당이나 복사 없이 반환할 수 있어 CSV, Markdown, JSON 파싱 등에서 중간 할당과 복사를 피하고 효율성을 높일 수 있습니다. 현대 언어와 주요 프레임워크들은 이미 이 방식을 채택하고 있습니다.
물론 기존 C 라이브러리 및 운영체제(OS) API와의 호환성을 위해 NUL 종료 문자열로의 변환이 필요할 수 있습니다. 하지만 대부분의 코드에서는 길이 기반 문자열에 불변성(immutability) 원칙을 결합하는 것이 더 단순하고 유연하다는 것이 전문가들의 의견입니다. 문자열을 한 번 생성하면 내용을 변경하지 않고 상수처럼 다루며, 함수 시그니처와 API 경계에서 불변성을 보장함으로써 안정성과 예측 가능성을 높일 수 있습니다. 이러한 설계는 현대 소프트웨어 개발에서 더욱 견고하고 효율적인 시스템을 구축하는 데 중요한 역할을 합니다.