programing

null 끝 문자열의 근거는 무엇입니까?

newsource 2022. 8. 16. 23:25

null 끝 문자열의 근거는 무엇입니까?

나는 C와 C++를 좋아하지만 Null 종단 문자열 선택에 머리를 긁적거리지 않을 수 없다.

  • 길이 접두사(예: Pascal) 문자열이 C 이전에 존재했습니다.
  • 길이 프리픽스 문자열은 일정한 시간 길이의 검색을 가능하게 하여 여러 알고리즘을 고속화합니다.
  • 프리픽스 문자열의 길이를 지정하면 버퍼 오버런 에러가 발생하기 어려워집니다.
  • 32비트 머신에서도 문자열이 사용 가능한 메모리의 사이즈로 되어 있는 경우, 길이 프리픽스 문자열은 늘 종단 문자열보다 3바이트밖에 넓지 않습니다.16비트 머신에서는 이것은 1바이트입니다.64비트 머신에서는 4GB가 적당한 문자열 길이 제한이지만 보통 64비트 머신에는 충분한 메모리가 있기 때문에 추가 7바이트는 null 인수입니다.원래 C 규격은 (메모리의 관점에서) 엄청나게 열악한 기계용으로 작성되었지만, 효율성에 대한 논의는 여기서 납득할 수 없습니다.
  • 거의 모든 다른 언어(Perl, Pascal, Python, Java, C# 등)는 길이 접두사 문자열을 사용합니다.이들 언어는 문자열 조작 벤치마크에서 보통 C를 앞섭니다.이것은 문자열이 더 효율적이기 때문입니다.
  • C++는 이 문제를 수정했습니다.std::basic_string템플릿이지만 Null로 종료된 문자열이 예상되는 일반 문자 배열은 여전히 널리 사용되고 있습니다.또한 힙 할당이 필요하기 때문에 불완전합니다.
  • Null 종단 문자열은 문자열에 존재할 수 없는 문자(즉, null)를 예약해야 하며, 길이 접두사 문자열에는 포함된 null을 포함할 수 있습니다.

이들 중 몇 가지는 C보다 최근에 밝혀졌기 때문에 C가 몰랐던 것이 타당합니다.그러나 C가 생기기 훨씬 전부터 몇 개는 분명했다.왜 null 종단 문자열이 명백히 큰 길이의 프레픽스 대신 선택되었을까요?

편집: 위의 효율성에 대해 몇 가지 사실을 묻는 사람도 있었지만(이미 제공한 내용은 마음에 들지 않았습니다), 몇 가지 점에서 비롯된 것입니다.

  • null 종단 문자열을 사용하려면 O(n + m) 시간의 복잡성이 필요합니다.길이 프리픽스에는 많은 경우 O(m)만 필요합니다.
  • Null 종단 문자열을 사용하려면 O(n) 시간의 복잡성이 필요합니다.길이 프리픽스는 O(1)입니다.
  • length와 concat는 단연코 가장 일반적인 문자열 연산입니다.Null 종단 문자열이 더 효율적일 수 있는 경우가 몇 가지 있지만 이러한 문자열은 훨씬 더 자주 발생하지 않습니다.

다음 답변에 따르면 Null 종단 문자열이 더 효율적인 경우가 있습니다.

  • 문자열의 시작을 잘라내고 어떤 방법으로 전달해야 할 때.길이 프리픽스는 원래 문자열을 파기할 수 있는 경우에도 일정 시간 내에 실행할 수 없습니다.길이 프리픽스는 아마 정렬 규칙을 따라야 하기 때문입니다.
  • 문자열을 한 글자씩 반복하는 경우 CPU 레지스터를 저장할 수 있습니다.이 방법은 스트링을 동적으로 할당하지 않은 경우에만 작동합니다(이 경우 스트링을 해방해야 하므로 원래 malloc 및 친구로부터 얻은 포인터를 유지하기 위해 저장한 CPU 레지스터를 사용해야 합니다).

위의 어떤 것도 길이와 콘센트만큼 흔하지 않다.

아래 답변에 하나 더 있습니다.

  • 끈의 끝을 잘라내야 합니다.

이 문자열은 올바르지 않습니다. Null 끝 문자열과 길이 접두사 문자열의 시간은 동일합니다.(늘 종단 문자열은 새로운 엔드를 원하는 위치에 늘을 붙이고 길이 프리픽스는 프리픽스에서 빼기만 하면 됩니다).

소식통으로부터

BCPL, B 또는 C 중 어느 것도 언어에서 문자 데이터를 강력하게 지원하지 않습니다.각 문자열은 정수의 벡터와 거의 동일하게 취급되며 몇 가지 규칙에 따라 일반적인 규칙을 보완합니다.BCPL과 B 모두에서 문자열 리터럴은 문자열의 문자로 초기화된 정적 영역의 주소를 나타내며 셀로 패킹됩니다.BCPL에서는 첫 번째 패킹된 바이트에는 문자열의 문자 수가 포함됩니다.B에서는 카운트는 없고 문자열은 특수 문자로 끝납니다.특수 문자는 B의 철자입니다.*e이 변경은 8비트 또는9비트 슬롯에서 카운트를 유지함으로써 발생하는 문자열 길이 제한을 피하기 위해 이루어졌으며, 이는 부분적으로 카운트를 유지하는 것이 터미네이터를 사용하는 것보다 덜 편리하다고 생각되었기 때문입니다.

Dennis M Ritchie, C 언어 개발

이 질문은 다음과 같이 질문됩니다.Length Prefixed Strings (LPS)zero terminated strings (SZ)단, 길이 프리픽스 문자열의 장점을 대부분 알 수 있습니다.압도적으로 보일 수 있지만 솔직히 LPS의 단점과 SZ의 장점도 고려해야 합니다.

내가 알기론, 이 질문은 심지어 "제로 종단 문자열의 장점은 무엇인가?"라고 묻는 편향된 방법으로도 이해될 수 있다.

종단 문자열이 제로인 장점(알겠습니다):

  • 매우 심플하고 언어에 새로운 개념을 도입할 필요가 없으며 char 배열/char 포인터도 가능합니다.
  • 핵심 언어에는 이중 따옴표 사이에 있는 것을 여러 개의 문자로 변환하기 위한 최소한의 구문설탕이 포함되어 있습니다(실제로 많은 바이트).경우에 따라서는 텍스트와 전혀 관련이 없는 것을 초기화하기 위해 사용할 수 있습니다.예를 들어 xpm 이미지 파일 형식은 문자열로 인코딩된 이미지 데이터를 포함하는 유효한 C 소스입니다.
  • 참고로 문자열 리터럴에 0을 넣을 수 있습니다.컴파일러는 리터럴 끝에 다른 1을 추가합니다."this\0is\0valid\0C"줄이야? 네 줄이야?아니면 많은 바이트를...
  • 플랫 실장, 숨김 간접, 숨김 정수 없음.
  • 숨겨진 메모리 할당은 포함되지 않습니다(strdup과 같은 악명 높은 비표준 함수는 할당을 수행하지만 대부분 문제의 원인입니다).
  • 소형 또는 대형 하드웨어에 대한 특별한 문제는 없습니다(8비트 마이크로컨트롤러에서 32비트 프리픽스 길이를 관리해야 하는 부담이나 문자열 크기를 256바이트 이하로 제한해야 하는 제한은 Turbo Pascal에서 실제로 겪었던 문제였습니다).
  • 문자열 조작의 구현은 매우 간단한 라이브러리 기능 중 몇 가지에 불과하다.
  • string의 주요 사용에 효율적 : 기존 시작부터 순차적으로 읽히는 고정 텍스트(사용자에게 메시지를 전달합니다).
  • 0을 끝내는 것은 필수 사항이 아닙니다. 바이트 다발처럼 문자를 조작하는 데 필요한 모든 도구를 사용할 수 있습니다.C에서 어레이 초기화를 수행할 경우 NUL 터미네이터를 피할 수도 있습니다.그냥 적당한 사이즈만 맞추세요. char a[3] = "foo";는 유효한 C(C++가 아님)이며 a에 마지막 0을 넣지 않습니다.
  • stdin, stdout과 같은 고유 길이가 없는 "files"를 포함하여 "everything is file"이라는 유닉스 관점과 일관됩니다.오픈 읽기 및 쓰기 프리미티브는 매우 낮은 수준에서 구현됩니다.라이브러리 콜이 아니라 시스템콜입니다또한 바이너리 파일이나 텍스트 파일에도 동일한 API가 사용됩니다.기본 파일을 읽는 파일은 버퍼 주소와 크기를 가져오고 새 크기를 반환합니다.그리고 문자열을 쓰기 위한 버퍼로 사용할 수 있습니다.다른 종류의 문자열 표현을 사용하면 출력 버퍼로서 리터럴 문자열을 쉽게 사용할 수 없거나, 또는 그것을 송신할 때 매우 이상한 동작을 하도록 할 필요가 있습니다.char*즉, 문자열의 주소를 반환하는 것이 아니라 실제 데이터를 반환하는 것입니다.
  • 불필요한 버퍼 복사 없이 파일에서 읽어낸 텍스트 데이터를 쉽게 조작할 수 있습니다.제로를 적절한 위치에 삽입하기만 하면 됩니다(오늘날 큰따옴표로 묶인 문자열은 보통 수정할 수 없는 데이터 세그먼트에 보관되어 있기 때문에 현대 C에서는 그렇지 않습니다).
  • 크기에 관계없이 일부 int 값을 선행하는 것은 정렬 문제를 의미합니다.첫 번째 길이는 정렬해야 하지만 문자 데이터에는 정렬할 필요가 없습니다(또한 문자열의 정렬을 강제하는 것은 바이트 묶음으로 취급할 때 문제가 있음을 의미합니다).
  • length는 컴파일 시 일정한 리터럴 문자열(size of)에 대해 알려져 있습니다.그럼 왜 사람들은 그것을 실제 데이터에 앞서서 메모리에 저장하려고 했을까요?
  • C가 (거의) 다른 모든 사람과 같이 하는 방식으로 문자열은 char 배열로 간주됩니다.어레이 길이는 C에서 관리되지 않으므로 문자열에 대해서도 논리적 길이가 관리되지 않습니다.유일하게 놀라운 것은 마지막에 0개의 항목이 추가되었다는 것입니다만, 큰따옴표 사이에 문자열을 입력할 때는 핵심 언어 수준입니다.사용자는 길이 전달되는 문자열 조작 함수를 완벽하게 호출하거나 일반 memcopy를 대신 사용할 수 있습니다.SZ는 그냥 시설이야.대부분의 다른 언어에서 어레이 길이는 관리되며 문자열과 동일한 논리입니다.
  • 어쨌든 현대에서는 1바이트 문자 집합으로는 충분하지 않고 문자 수가 바이트 수와 매우 다른 인코딩된 유니코드 문자열을 처리해야 하는 경우가 많습니다.이는 사용자가 "크기" 이상의 정보를 원할 수 있음을 의미합니다.길이를 유지하면 이러한 다른 유용한 정보에 대해 아무 것도 사용할 수 없습니다(특히 데이터를 저장할 수 있는 자연적 장소가 없음).

단, 표준 C 문자열이 실제로 비효율적인 드문 경우에는 불평할 필요가 없습니다.리브도 있어요.만약 내가 그 트렌드를 따랐다면, 표준 C에는 regex 지원 기능이 포함되어 있지 않다고 불평할 것이다.하지만, 그러한 목적을 위한 도서관이 있기 때문에, 실제로는 문제가 되지 않는다는 것을 모두가 알고 있습니다.따라서 문자열 조작 효율이 요구될 때 bstring과 같은 라이브러리를 사용하는 것은 어떻습니까?아니면 C++ 문자열도?

편집: 최근에 D스트링을 살펴봤습니다.선택한 솔루션이 크기 프리픽스도 제로 끝도 아님을 알 수 있을 정도로 흥미롭습니다.C와 같이 큰따옴표로 둘러싸인 리터럴 문자열은 불변의 char 배열에 대한 짧은 글씨일 뿐이며 언어에는 (불변의 char 배열)을 의미하는 string 키워드도 있습니다.

그러나 D 어레이는 C 어레이보다 훨씬 풍부합니다.정적 배열의 경우 런타임에 길이를 알 수 있으므로 길이를 저장할 필요가 없습니다.컴파일러는 컴파일 시에 그것을 가지고 있다.동적 어레이의 경우 길이를 사용할 수 있지만 D 문서에는 저장 위치가 명시되어 있지 않습니다.우리가 아는 한 컴파일러는 그것을 어떤 레지스터에 보관하거나 문자 데이터에서 멀리 떨어진 곳에 저장된 변수에 보관하도록 선택할 수 있습니다.

일반 문자 배열 또는 비 리터럴 문자열에는 최종 0이 없기 때문에 프로그래머가 D에서 C 함수를 호출하려면 그 자체를 넣어야 합니다.특정 리터럴 문자열의 경우 D 컴파일러는 여전히 각 문자열 끝에 0을 붙입니다(C 문자열에 쉽게 캐스트하여 C 함수를 호출할 수 있도록 하기 위해 ?). 그러나 이 0은 문자열의 일부가 아닙니다(D는 문자열 크기로 계산하지 않습니다).

유일하게 아쉬운 점은 문자열은 utf-8이어야 하지만 길이가 멀티바이트 문자를 사용하더라도 여전히 바이트 수를 반환한다는 것입니다(적어도 제 컴파일러 gdc에서는 해당).컴파일러의 버그인지 의도인지 불분명합니다.(알겠습니다)D 컴파일러에게 당신의 소스를 사용하려면 utf-8을 사용해야 합니다.처음에 멍청한 바이트 순서 마크를 붙여야 합니다.특히 ASCII 호환성이 있는 UTF-8의 경우 편집자가 그렇게 하지 않는 것을 알기 때문에 바보라고 쓰고 있습니다).

C에는 언어의 일부로서 문자열이 없습니다.C의 문자열은 char에 대한 포인터일 뿐입니다.그래서 아마 당신은 잘못된 질문을 하고 있는 것 같아요.

"문자열 유형을 생략하는 근거는 무엇입니까?"가 더 적절할 수 있습니다.C는 객체 지향 언어가 아니며 기본적인 가치 유형만을 가지고 있다는 것을 지적하고 싶습니다.문자열은 다른 유형의 값을 조합하여 구현해야 하는 상위 수준의 개념입니다.C는 추상화의 하위 단계입니다.

아래쪽의 맹렬한 스콜에 비춰보면:

제가 지적하고 싶은 것은 이것이 어리석거나 나쁜 질문이라는 것, 또는 문자열을 표현하는 C 방식이 최선의 선택이라는 것을 말하려는 것이 아닙니다.C에 데이터형으로서의 문자열을 바이트 배열과 구별하는 메커니즘이 없다는 점을 고려한다면 질문이 더 간결하게 정리될 수 있다는 점을 명확히 하려고 합니다.오늘날 컴퓨터의 처리능력과 메모리 성능을 고려하면 이것이 최선의 선택입니까?아마 아닐 것입니다.하지만 나중에 생각하면 항상 20/20이고, 그 모든 것이 전부입니다. :)

역사적인 이유가 있어서 위키피디아에서 찾아냈다고 생각합니다.

C(및 C에서 파생된 언어)가 개발되었을 때 메모리는 매우 제한적이었기 때문에 문자열의 길이를 저장하기 위해 오버헤드를 1바이트만 사용하는 것이 매력적이었다.그 당시에 유일하게 널리 사용되었던 대안으로 일반적으로 "Pascal 문자열"이라고 불리는 것은 (BASIC의 초기 버전에서도 사용되었지만) 문자열의 길이를 저장하기 위해 선행 바이트를 사용했다.이것에 의해, 문자열에 NUL 를 포함할 수 있게 되어, 길이를 검출하려면 1 개의 메모리 액세스(O(1)(정수) 시간) 밖에 필요 없게 됩니다.단, 1바이트는 길이를 255로 제한합니다.이 길이 제한은 C 문자열의 문제보다 훨씬 더 제한적이기 때문에 일반적으로 C 문자열이 유리합니다.

칼라베라옳습니다만, 사람들이 그의 요점을 이해하지 못하는 것 같기 때문에, 몇 가지 코드 예를 들어 보겠습니다.

먼저 C가 무엇인지 생각해 봅시다.모든 코드가 기계어로 직접 번역되는 단순한 언어입니다.모든 타입이 레지스터와 스택에 들어가 운영체제나 대규모 런타임 라이브러리가 필요 없습니다.이는 이러한 내용을 작성하기 위한 것이었기 때문입니다(이 작업은 오늘날까지 경쟁 상대가 없다는 점을 고려하면 매우 적합합니다).

C의 경우string타이프, 예를 들어int또는char레지스터나 스택에 들어가지 않는 타입으로, 메모리 할당(지원 인프라스트럭처의 모든 것)을 어떠한 방법으로도 처리할 필요가 있습니다.이 모든 것들은 C의 기본 원칙에 어긋난다.

C의 문자열은 다음과 같습니다.

char s*;

그러면 이 값이 길이 프리픽스라고 가정해 보겠습니다.2개의 스트링을 연결하기 위한 코드를 작성합니다.

char* concat(char* s1, char* s2)
{
    /* What? What is the type of the length of the string? */
    int l1 = *(int*) s1;
    /* How much? How much must I skip? */
    char *s1s = s1 + sizeof(int);
    int l2 = *(int*) s2;
    char *s2s = s2 + sizeof(int);
    int l3 = l1 + l2;
    char *s3 = (char*) malloc(l3 + sizeof(int));
    char *s3s = s3 + sizeof(int);
    memcpy(s3s, s1s, l1);
    memcpy(s3s + l1, s2s, l2);
    *(int*) s3 = l3;
    return s3;
}

또 다른 방법으로는 스트링을 정의하는 구조를 사용하는 방법이 있습니다.

struct {
  int len; /* cannot be left implementation-defined */
  char* buf;
}

이 시점에서 모든 문자열 조작에는 2개의 할당이 필요합니다.실제로 이 할당은 라이브러리를 통해 처리할 수 있습니다.

웃긴 건...그런 구조물은 C에 존재합니다!사용자 처리에 대한 메시지를 매일 표시하는 데 사용되지 않을 뿐입니다.

Calavera의 요점은 다음과 같습니다.C에는 문자열 타입이 없습니다.이 기능을 사용하려면 포인터를 가져와 두 가지 다른 유형의 포인터로 디코딩해야 합니다. 그러면 문자열의 크기가 어떻게 되는지 매우 관련성이 높아지므로 "실장 정의"로 둘 수 없습니다.

이제 C는 메모리를 처리할 수 있습니다.mem라이브러리 내의 기능(내)<string.h>, even!)는 메모리를 처리하는 데 필요한 모든 도구를 포인터와 크기의 쌍으로 제공합니다.C의 소위 "스트링"은 오직 하나의 목적으로 작성되었습니다. 텍스트 단말기를 위한 운영체제를 쓰는 컨텍스트에서 메시지를 보여줍니다.그러기 위해서는 null 종료로 충분합니다.

아직 언급되지 않은 점: C가 설계되었을 때 'char'가 8비트가 아닌 기계가 다수 있었습니다(현재도 'char'가 아닌 DSP 플랫폼이 있습니다).문자열의 길이를 프리픽스로 하는 경우는, 「char」의 길이의 프리픽스를 몇 개 사용할 필요가 있습니까.2개를 사용하면 8비트 문자 및 32비트 주소 지정 공간이 있는 머신에서는 문자열 길이에 인위적인 제한이 가해지고 16비트 문자 및 16비트 주소 지정 공간이 있는 머신에서는 공간이 낭비됩니다.

임의의 길이의 스트링을 효율적으로 저장할 수 있도록 하고, 또 'char'가 항상8비트일 경우 속도와 코드사이즈 면에서 어느 정도 비용을 들여 짝수 N이 프리픽스 되는 스트링을 N/2바이트로 정의하고, 홀수값 N과 짝수값 M(읽기)을 프리픽스 하는 스트링을 정의할 수 있습니다(N_1+char).를 참조해 주세요.따라서 문자열을 보유하기 위한 일정한 공간을 제공하는 버퍼는 그 공간 앞에 최대 길이를 처리할 수 있는 충분한 바이트를 허용해야 합니다.그러나 문자열 길이를 유지하기 위해 필요한 "char"의 수는 CPU 아키텍처에 따라 다르기 때문에 "char"가 항상 8비트인 것은 아닙니다.

"C는 끈이 없다"는 대답은 믿지 않는다.사실 C는 기본 제공 상위 레벨 유형을 지원하지 않지만 데이터 구조를 C로 나타낼 수 있습니다. 이것이 문자열입니다.문자열이 C의 포인터일 뿐이라는 사실이 첫 번째 N바이트가 길이로서 특별한 의미를 가질 수 없다는 것을 의미하지는 않습니다.

Windows/COM 개발자는 다음 사항에 매우 익숙할 것입니다.BSTR타입은 다음과 같습니다.실제 문자 데이터가 바이트0이 아닌 길이 프리픽스 C 문자열입니다.

그래서 무효 종료를 사용하는 결정은 단순히 사람들이 선호하는 것이지 언어의 필요성은 아닌 것 같다.

물론 퍼포먼스와 안전을 위해 스트링의 길이를 유지한 채 작업을 반복하는 것이 아니라strlen또는 그에 상당하는 것.그러나 문자열 내용 직전에 길이를 고정 위치에 저장하는 것은 매우 나쁜 설계입니다.Jörgen이 Sanjit의 답변에 대한 코멘트에서 지적했듯이, 그것은 문자열의 꼬리를 문자열로 취급하는 것을 배제하고, 예를 들어 다음과 같은 많은 일반적인 연산을 만듭니다.path_to_filename또는filename_to_extension새로운 메모리를 할당하지 않으면 불가능합니다(또, 장해나 에러 처리의 가능성도 있습니다).그리고 물론 문자열 길이 필드가 차지하는 바이트 수에 대해서는 아무도 동의할 수 없는 문제가 있습니다(많은 "Pascal 문자열" 언어가 16비트 필드 또는 긴 문자열을 처리할 수 없는 24비트 필드를 사용했습니다).

프로그래머가 길이를 저장할 것인지, 어디에 저장할 것인지 선택할 수 있도록 하는 C의 디자인은 훨씬 유연하고 강력합니다.하지만 물론 프로그래머는 똑똑해야 한다.C는 충돌, 정지 또는 적에게 뿌리를 내리는 프로그램으로 어리석음을 처벌합니다.

반드시 근거는 아니지만 길이 인코딩에 대한 대척점

  1. 동적 길이 부호화의 일부 형식은 정적 길이 부호화보다 우수하며, 메모리에 관한 한 이는 모두 사용 상황에 따라 달라집니다.UTF-8을 보고 증거를 찾아보세요.기본적으로 단일 문자를 인코딩하기 위한 확장 가능한 문자 배열입니다.확장 바이트마다 1비트가 사용됩니다.NUL 터미네이션은 8비트를 사용합니다.length-prefix는 64비트를 사용하는 것으로도 무한길이라고 할 수 있다고 생각합니다.여분의 비트의 케이스에 얼마나 자주 부딪히느냐가 결정적 요소입니다.아주 큰 문자열이 하나뿐입니까?8비트든 64비트든 상관없습니다.작은 문자열(영어의 문자열)이 많습니까?그러면 프리픽스 비용이 큰 비율을 차지합니다.

  2. 시간 절약을 가능하게 하는 길이 프리픽스 문자열은 현실적이지 않습니다.제공된 데이터의 길이를 제공해야 하는지, 컴파일 시 계산해야 하는지, 아니면 문자열로 인코딩해야 하는 동적 데이터를 제공해야 하는지 여부입니다.이러한 사이즈는 알고리즘의 어느 시점에서 계산됩니다.Null 종단 문자열의 크기를 저장하는 별도의 변수를 제공할 수 있습니다.그래서 시간 절약에 대한 비교가 되는군한 명은 마지막에 NUL이 하나 더...그러나 길이 부호화에 해당 NUL이 포함되지 않으면 둘 사이에는 문자 그대로 차이가 없습니다.알고리즘 변경은 전혀 필요 없습니다.프리패스만 하면 컴파일러/런타임에 직접 설계하지 않아도 됩니다.C는 주로 수동으로 작업을 수행하는 것입니다.

  3. 길이 프리픽스가 옵션인 것이 판매 포인트입니다.알고리즘에 필요한 추가 정보가 반드시 필요한 것은 아니기 때문에 모든 문자열에 대해 이 작업을 수행해야 하기 때문에 프리컴퓨팅+컴퓨팅 시간이 O(n) 아래로 떨어질 수 없습니다.(즉, 하드웨어 난수 발생기 1-128).'무한줄'에서 뽑을 수 있어요.문자 생성 속도가 너무 빠르다고 칩시다.그래서 끈 길이가 항상 바뀌어요.하지만 데이터 사용량은 랜덤 바이트 수에 관계없이 사용할 수 있습니다.요청 후 바로 사용할 수 있는 다음 미사용 바이트를 원할 뿐입니다.내가 장치를 기다리고 있을 수도 있어하지만 미리 읽어둔 글자의 버퍼도 만들 수 있습니다.길이 비교는 계산의 불필요한 낭비입니다.늘 체크가 더 효율적입니다.)

  4. length-prefix는 버퍼 오버플로를 방지하는 좋은 방법입니까?라이브러리 기능과 구현의 올바른 사용도 마찬가지입니다.잘못된 형식의 데이터를 전달하면 어떻게 됩니까?버퍼의 길이는 2바이트이지만 함수는 7이라고 합니다.예: gets()가 기존의 데이터에 사용되도록 되어 있었다면 컴파일된 버퍼와 malloc() 콜을 테스트하고 스펙을 따르는 내부 버퍼 체크가 있었을 가능성이 있습니다.알 수 없는 STDIN이 알 수 없는 버퍼에 도달하기 위한 파이프로 사용되는 경우 버퍼 크기를 알 수 없습니다. 즉, 길이 arg는 무의미합니다.여기서는 카나리 체크와 같은 다른 것이 필요합니다.이 경우 일부 스트림과 입력의 길이를 프리픽스할 수 없습니다.즉, 길이 체크는 알고리즘에 내장되어야 합니다. 타이핑 시스템의 마법적인 부분이 아닙니다.TL;DR NUL 종료는 안전하지 않을 필요가 없습니다.그것은 오용에 의한 것입니다.

  5. 카운터 포인트: NUL 종료는 바이너리에서는 귀찮습니다.여기서 length-prefix를 실행하거나 이스케이프 코드, 범위 재매핑 등의 방법으로 NUL 바이트를 변환해야 합니다.이는 물론 메모리 용량/메모리 용량 증가/바이트당 연산 증가(more-operations per-byte)를 의미합니다.길이 프리픽스가 대부분 전쟁에서 승리합니다.변환의 유일한 장점은 길이 프리픽스 문자열을 커버하기 위해 추가 함수를 쓸 필요가 없다는 것입니다.즉, 보다 최적화된 서브 O(n) 루틴에서는 코드를 추가하지 않고 O(n) 등가물로 자동으로 동작하도록 할 수 있습니다.단점은 물론 NUL 무거운 문자열에서 사용할 경우 시간/메모리/압축이 낭비된다는 것입니다.바이너리 데이터로 동작하기 위해서 어느 정도의 라이브러리를 복제하는지에 따라서는, 길이 프리픽스 문자열만으로 동작하는 것이 적절한 경우가 있습니다. 즉, length-prefix 문자열에도 같은 처리를 할 수 있습니다.- 1 length는 NUL 종단 문자열이 될 수 있습니다.또, length 종단 문자열은 내부에서 NUL 종단 문자열을 사용할 수 있습니다.

  6. Concat: "O(n+m) vs O(m)" 연결 후 m을 문자열의 총 길이라고 가정합니다. 왜냐하면 둘 다 최소 작업 수를 가져야 하기 때문입니다(문자열 1에 고정할 수는 없습니다. 재할당을 해야 한다면 어떻게 해야 할까요?).그리고 n은 사전 컴퓨팅 때문에 더 이상 할 필요가 없는 가공의 작업량이라고 생각합니다.그렇다면 답은 간단합니다. 사전 계산입니다.재할당하지 않아도 될 만큼 충분한 메모리가 있다고 주장하는 경우, 이것이 Big-O 표기법의 기본이라면 답은 훨씬 더 간단합니다. 즉, 할당된 메모리에서 문자열 1의 끝 부분에 대해 이진 검색을 수행합니다. 문자열 1 뒤에 무한 제로 스왓치가 대량으로 존재하므로 재할당을 걱정할 필요가 없습니다.거기서 쉽게 n을 기록하려고 했고 거의 시도하지 않았다.로그(n)는 실제 컴퓨터에서는 64개밖에 되지 않습니다.이것은 본질적으로 O(64+m)라고 하는 것과 같습니다.(그리고 그 논리는 현재 사용되고 있는 실제 데이터 구조의 런타임 분석에 사용되고 있습니다.)즉석에서 헛소리가 아닙니다.)

  7. Concat()/Len() : 결과를 메모합니다.간단. 모든 컴퓨팅을 가능하면 사전 컴퓨팅으로 변환합니다.이것은 알고리즘에 의한 결정입니다.그것은 언어의 강제적인 제약이 아닙니다.

  8. 문자열 서픽스는 NUL 종료로 전달이 용이하거나 가능합니다.length-prefix의 실장 방법에 따라서는 원래의 문자열이 파괴될 수 있으며, 경우에 따라서는 불가능할 수도 있습니다.O(1)가 아닌 O(n)를 복사하여 전달해야 합니다.

  9. NUL 종단 프리픽스에 비해 인수 전달/참조 취소가 적습니다.분명히 당신이 정보를 덜 전달하고 있기 때문입니다.길이가 필요 없는 경우 설치 공간을 크게 절약하고 최적화할 수 있습니다.

  10. 당신은 속일 수 있어요.이건 그냥 포인터일 뿐이에요.누가 그걸 끈으로 읽어야 한다고 그래요?한 글자 또는 플로트로 읽고 싶다면?반대로 플로트를 끈으로 읽고 싶다면?조심하면 NUL-termination으로 할 수 있습니다.length-prefix로는 이 작업을 수행할 수 없습니다. 일반적으로 포인터와 확연히 다른 데이터 유형입니다.대부분의 경우 바이트 단위로 문자열을 작성하고 길이를 구해야 합니다.물론 플로트 전체를 원하신다면(아마도 NUL이 들어가 있을 것입니다) 바이트 단위로 읽으셔야 합니다만, 자세한 내용은 고객님이 결정하셔야 합니다.

TL;DR 이진 데이터를 사용하고 있습니까?no의 경우 NUL 종단에 의해 알고리즘의 자유도가 높아집니다.그렇다면 코드 수 대 속도/메모리/압축이 주요 관심사입니다.두 가지 접근방식을 조합하거나 메모화하는 것이 가장 좋을 수 있습니다.

게으름, 모든 언어의 어셈블리 GUT, 특히 어셈블리보다 한 단계 높은 C(따라서 많은 어셈블리 레거시 코드를 상속받음)를 고려하여 알뜰함과 휴대성을 등록합니다.이러한 ASCII 기간에는 늘 문자가 무용지물입니다(또한 EOF 컨트롤 문자도 마찬가지일 수 있습니다).

유사 코드로 봅시다.

function readString(string) // 1 parameter: 1 register or 1 stact entries
    pointer=addressOf(string) 
    while(string[pointer]!=CONTROL_CHAR) do
        read(string[pointer])
        increment pointer

총 1개의 레지스터 사용

케이스 2

 function readString(length,string) // 2 parameters: 2 register used or 2 stack entries
     pointer=addressOf(string) 
     while(length>0) do 
         read(string[pointer])
         increment pointer
         decrement length

총 2개의 레지스터 사용

그 당시에는 근시안적으로 보일 수 있지만 코드와 레지스터의 검소함을 고려하면(그 당시에는 프리미엄이었지만, 그 당시에는 펀치 카드를 사용하고 있었습니다).따라서 (프로세서 속도를 kHz로 계산할 수 있을 때) 이 "핵"은 매우 좋았고 등록이 필요 없는 프로세서를 쉽게 휴대할 수 있었습니다.

For argument sake I will implement 2 common string operation

stringLength(string)
     pointer=addressOf(string)
     while(string[pointer]!=CONTROL_CHAR) do
         increment pointer
     return pointer-addressOf(string)

complexity O(n) where in most case PASCAL string is O(1) because the length of the string is pre-pended to the string structure (that would also mean that this operation would have to be carried in an earlier stage).

concatString(string1,string2)
     length1=stringLength(string1)
     length2=stringLength(string2)
     string3=allocate(string1+string2)
     pointer1=addressOf(string1)
     pointer3=addressOf(string3)
     while(string1[pointer1]!=CONTROL_CHAR) do
         string3[pointer3]=string1[pointer1]
         increment pointer3
         increment pointer1
     pointer2=addressOf(string2)
     while(string2[pointer2]!=CONTROL_CHAR) do
         string3[pointer3]=string2[pointer2]
         increment pointer3
         increment pointer1
     return string3

complexity O(n) and prepending the string length wouldn't change the complexity of the operation, while I admit it would take 3 time less time.

On another hand, if you use PASCAL string you would have to redesign your API for taking in account register length and bit-endianness, PASCAL string got the well known limitation of 255 char (0xFF) beacause the length was stored in 1 byte (8bits), and it you wanted a longer string (16bits->anything) you would have to take in account the architecture in one layer of your code, that would mean in most case incompatible string APIs if you wanted longer string.

Example:

One file was written with your prepended string api on an 8 bit computer and then would have to be read on say a 32 bit computer, what would the lazy program do considers that your 4bytes are the length of the string then allocate that lot of memory then attempt to read that many bytes. Another case would be PPC 32 byte string read(little endian) onto a x86 (big endian), of course if you don't know that one is written by the other there would be trouble. 1 byte length (0x00000001) would become 16777216 (0x0100000) that is 16 MB for reading a 1 byte string. Of course you would say that people should agree on one standard but even 16bit unicode got little and big endianness.

Of course C would have its issues too but, would be very little affected by the issues raised here.

One advantage of NUL-termination over length-prefixing, which I have not seen anyone mention, is the simplicity of string comparison. Consider the comparison standard which returns a signed result for less-than, equal, or greater-than. For length-prefixing the algorithm has to be something along the following lines:

  1. Compare the two lengths; record the smaller, and note if they are equal (this last step might be deferred to step 3).
  2. 일치하는 인덱스에서 문자를 뺀 두 문자 시퀀스를 스캔합니다(또는 듀얼 포인터 스캔 사용).차이가 0이 아닌 경우, 차이를 반환하거나 스캔한 문자 수가 더 작은 길이와 같을 경우 중지합니다.
  3. 길이가 작아지면, 1개의 문자열이 다른 문자열의 프리픽스가 됩니다.어느 쪽이 짧은지에 따라 음수 또는 양의 값을 반환하고, 같은 길이의 경우 0을 반환합니다.

이것을 NUL 종단 알고리즘과 대조합니다.

  1. 일치하는 인덱스에서 문자를 뺀 두 문자 시퀀스를 스캔합니다(이동 포인터로 더 잘 처리됩니다).차이가 0이 아니면 중지하고 차이를 반환합니다.메모: 하나의 문자열이 다른 문자열의 PROPLE 접두사일 경우 감산된 문자 중 하나는 NUL(즉, 0)이 되며 비교는 여기서 자연스럽게 중단됩니다.
  2. 차이가 0인 경우 -only - 어느 한 문자가 NUL인지 확인합니다.이 경우 0을 반환하고 그렇지 않으면 다음 문자로 계속 진행합니다.

NUL로 종료된 케이스는 듀얼 포인터 스캔을 통해 보다 심플하고 효율적으로 구현하기 쉽습니다.길이 프리픽스 케이스는 적어도 같은 양의 작업을 수행합니다.거의 항상.알고리즘이 많은 문자열 비교(예를 들어 컴파일러!)를 수행해야 하는 경우 NUL로 종료된 경우가 우선합니다.요즘은 그게 그렇게 중요하지 않을 수도 있지만 옛날에는 그렇지

C를 둘러싼 많은 설계 결정은 C가 처음 구현되었을 때 파라미터 전달에 다소 비용이 많이 든다는 사실에서 비롯되었습니다.예를 들어, 둘 중 하나를 선택할 수 있습니다.

void add_element_to_next(arr, offset)
  char[] arr;
  int offset;
{
  arr[offset] += arr[offset+1];
}

char array[40];

void test()
{
  for (i=0; i<39; i++)
    add_element_to_next(array, i);
}

void add_element_to_next(ptr)
  char *p;
{
  p[0]+=p[1];
}

char array[40];

void test()
{
  int i;
  for (i=0; i<39; i++)
    add_element_to_next(arr+i);
}

그것이 두 아니라 한 매개 변수 전달이 필요한 후자 약간(그리고 이처럼 선호하는)것이 비용이 덜 들었겠지만.메서드가 호출되는 배열의 기본 주소나, 내에 있는 인덱스를 알려, 하나의 포인터가 두 결합하는 필요가 없어 별도로 값을 통과하는 것보다 더 저렴할 것이다.

거기에 있는 동안에 C인코딩 문자열 길이를 가질 수 있는 많은 합리적인가지 방법이 있는 그때까지 발명되였다는 접근법은 문자열의 부분으로 된 2개의 별개 매개 변수로 문자열의 기본 주소와 원하는 지수를 받아들일 일할 수 있어야 하는 모든 필요한 기능을 할 것이다.0바이트 종결을 사용하는 것은 가능한 그 요구 사항은 막기 위해서다.비록 다른 접근법이 더 좋오늘날의 기계로(현대 컴파일러 종종 레지스터에 있고 memcpy 방법으로strcpy 최적화될 수 있는 매개 변수 패스()-equivalents 수 있지 않)충분히 프로덕션 코드를 사용하여 zero-byte 종료된 문자열을 것은 바꾸는 것이 아무것도 없어

PS--In exchange for a slight speed penalty on some operations, and a tiny bit of extra overhead on longer strings, it would have been possible to have methods that work with strings accept pointers directly to strings, bounds-checked string buffers, or data structures identifying substrings of another string. A function like "strcat" would have looked something like [modern syntax]

void strcat(unsigned char *dest, unsigned char *src)
{
  struct STRING_INFO d,s;
  str_size_t copy_length;

  get_string_info(&d, dest);
  get_string_info(&s, src);
  if (d.si_buff_size > d.si_length) // Destination is resizable buffer
  {
    copy_length = d.si_buff_size - d.si_length;
    if (s.src_length < copy_length)
      copy_length = s.src_length;
    memcpy(d.buff + d.si_length, s.buff, copy_length);
    d.si_length += copy_length;
    update_string_length(&d);
  }
}

A little bigger than the K&R strcat method, but it would support bounds-checking, which the K&R method doesn't. Further, unlike the current method, it would be possible to easily concatenate an arbitrary substring, e.g.

/* Concatenate 10th through 24th characters from src to dest */

void catpart(unsigned char *dest, unsigned char *src)
{
  struct SUBSTRING_INFO *inf;
  src = temp_substring(&inf, src, 10, 24);
  strcat(dest, src);
}

Note that the lifetime of the string returned by temp_substring would be limited by those of s and src, which ever was shorter (which is why the method requires inf to be passed in--if it was local, it would die when the method returned).

In terms of memory cost, strings and buffers up to 64 bytes would have one byte of overhead (same as zero-terminated strings); longer strings would have slightly more (whether one allowed amounts of overhead between two bytes and the maximum required would be a time/space tradeoff). A special value of the length/mode byte would be used to indicate that a string function was given a structure containing a flag byte, a pointer, and a buffer length (which could then index arbitrarily into any other string).

Of course, K&R didn't implement any such thing, but that's most likely because they didn't want to spend much effort on string handling--an area where even today many languages seem rather anemic.

gcc accept the codes below:

char s[4] = "abcd";

and it's ok if we treat is as an array of chars but not string. That is, we can access it with s[0], s[1], s[2], and s[3], or even with memcpy(dest, s, 4). But we'll get messy characters when we trying with puts(s), or worse with strcpy(dest, s).

In many ways, C was primitive. And I loved it.

It was a step above assembly language, giving you nearly the same performance with a language that was much easier to write and maintain.

The null terminator is simple and requires no special support by the language.

Looking back, it doesn't seem that convenient. But I used assembly language back in the 80s and it seemed very convenient at the time. I just think software is continually evolving, and the platforms and tools continually get more and more sophisticated.

Somehow I understood the question to imply there's no compiler support for length-prefixed strings in C. The following example shows, at least you can start your own C string library, where string lengths are counted at compile time, with a construct like this:

#define PREFIX_STR(s) ((prefix_str_t){ sizeof(s)-1, (s) })

typedef struct { int n; char * p; } prefix_str_t;

int main() {
    prefix_str_t string1, string2;

    string1 = PREFIX_STR("Hello!");
    string2 = PREFIX_STR("Allows \0 chars (even if printf directly doesn't)");

    printf("%d %s\n", string1.n, string1.p); /* prints: "6 Hello!" */
    printf("%d %s\n", string2.n, string2.p); /* prints: "48 Allows " */

    return 0;
}

This won't, however, come with no issues as you need to be careful when to specifically free that string pointer and when it is statically allocated (literal char array).

Edit: As a more direct answer to the question, my view is this was the way C could support both having string length available (as a compile time constant), should you need it, but still with no memory overhead if you want to use only pointers and zero termination.

Of course it seems like working with zero-terminated strings was the recommended practice, since the standard library in general doesn't take string lengths as arguments, and since extracting the length isn't as straightforward code as char * s = "abc", as my example shows.

"Even on a 32 bit machine, if you allow the string to be the size of available memory, a length prefixed string is only three bytes wider than a null terminated string."

First, extra 3 bytes may be considerable overhead for short strings. In particular, a zero-length string now takes 4 times as much memory. Some of us are using 64-bit machines, so we either need 8 bytes to store a zero-length string, or the string format can't cope with the longest strings the platform supports.

There may also be alignment issues to deal with. Suppose I have a block of memory containing 7 strings, like "solo\0second\0\0four\0five\0\0seventh". The second string starts at offset 5. The hardware may require that 32-bit integers be aligned at an address that is a multiple of 4, so you have to add padding, increasing the overhead even further. The C representation is very memory-efficient in comparison. (Memory-efficiency is good; it helps cache performance, for example.)

According to Joel Spolsky in this blog post,

It's because the PDP-7 microprocessor, on which UNIX and the C programming language were invented, had an ASCIZ string type. ASCIZ meant "ASCII with a Z (zero) at the end."

After seeing all the other answers here, I'm convinced that even if this is true, it's only part of the reason for C having null-terminated "strings". That post is quite illuminating as to how simple things like strings can actually be quite hard.

Assuming for a moment that C implemented strings the Pascal way, by prefixing them by length: is a 7 char long string the same DATA TYPE as a 3-char string? If the answer is yes, then what kind of code should the compiler generate when I assign the former to the latter? Should the string be truncated, or automatically resized? If resized, should that operation be protected by a lock as to make it thread safe? The C approach side stepped all these issues, like it or not :)

The null termination allows for fast pointer based operations.

I think the better question is why you think C owes you anything? C was designed to give you what you need, nothing more. You need to loose the mentality that the language must provide you with everything. Or just continue to use your higher level languages that will give you the luxary of String, Calendar, Containers; and in the case of Java you get one thing in tonnes of variety. Multiple types String, multiple types of unordered_map(s).

Too bad for you, this was not the purpose of C. C was not designed to be a bloated language that offers from a pin to an anchor. Instead you must rely on third party libraries or your own. And there is nothing easier than creating a simple struct that will contain a string and its size.

struct String
{
 const char *s;
 size_t len;
};

You know what the problem is with this though. It is not standard. Another language might decide to organize the len before the string. Another language might decide to use a pointer to end instead. Another might decide to use six pointers to make the String more efficient. However a null terminated string is the most standard format for a string; which you can use to interface with any language. Even Java JNI uses null terminated strings.

Lastly, it is a common saying; the right data structure for the task. If you find that need to know the size of a string more than anything else; well use a string structure that allows you to do that optimally. But don't make claims that that operation is used more than anything else for everybody. Like, why is knowing the size of a string more important than reading its contents. I find that reading the contents of a string is what I mostly do, so I use null terminated strings instead of std::string; which saves me 5 pointers on a GCC compiler. If I can even save 2 pointers that is good.

ReferenceURL : https://stackoverflow.com/questions/4418708/whats-the-rationale-for-null-terminated-strings