терминальный - функции string c++




Каково обоснование строк с нулевым завершением? (12)

«Даже на 32-битной машине, если вы разрешаете строке быть размером доступной памяти, длина префиксной строки всего на три байта шире, чем строка с нулевым завершением».

Во-первых, дополнительные 3 байта могут быть значительными накладными расходами для коротких строк. В частности, строка с нулевой длиной теперь занимает в 4 раза больше памяти. Некоторые из нас используют 64-битные машины, поэтому нам нужно 8 байтов для хранения строки нулевой длины, или формат строки не может справиться с самыми длинными строками, поддерживаемыми платформой.

Могут также возникать проблемы с выравниванием. Предположим, у меня есть блок памяти, содержащий 7 строк, например «solo \ 0second \ 0 \ 0four \ 0five \ 0 \ 0seventh". Вторая строка начинается со смещения 5. Аппаратное обеспечение может требовать, чтобы 32-разрядные целые числа были выровнены по адресу, кратное 4, поэтому вам нужно добавить отступы, увеличив накладные расходы еще больше. Представление C очень экономично для сравнения. (Эффективность работы с памятью хороша, например, она обеспечивает производительность кеша).

Насколько я люблю C и C ++, я не могу не почесать голову при выборе нулевых завершенных строк:

  • Строки с префиксом длины (т.е. Pascal) существовали до C
  • Строки с префиксом длины делают несколько алгоритмов быстрее, позволяя искать постоянную длину.
  • Строки с префиксом длины делают сложнее вызвать ошибки переполнения буфера.
  • Даже на 32-битной машине, если вы разрешаете строке быть размером доступной памяти, длина префиксной строки всего на три байта шире, чем строка с нулевым завершением. На 16-битных машинах это один байт. На 64-битных машинах 4 ГБ является разумным пределом длины строки, но даже если вы хотите расширить его до размера машинного слова, 64-разрядные машины обычно имеют достаточную память, что делает лишние семь байтов вроде нулевого аргумента. Я знаю, что оригинальный C-стандарт был написан для безумно бедных машин (с точки зрения памяти), но аргумент эффективности не продает меня здесь.
  • Практически каждый другой язык (например, Perl, Pascal, Python, Java, C # и т. Д.) Использует длину префикса. Эти языки обычно превзошли C в тестах строковых манипуляций, потому что они более эффективны со строками.
  • C ++ исправил это немного с std::basic_string шаблона std::basic_string , но простые массивы символов, ожидающие нулевых завершенных строк, все еще распространены. Это также несовершенно, потому что для этого требуется распределение кучи.
  • Строки с нулевым завершением должны резервировать символ (а именно, нуль), который не может существовать в строке, в то время как строки с префиксом длины могут содержать встроенные нули.

Некоторые из этих вещей появились недавно, чем C, поэтому было бы разумно, чтобы C не знал о них. Тем не менее, некоторые из них были хорошо известны до того, как C стал. Почему были выбраны нуль-прерванные строки вместо префикса явно превосходящей длины?

РЕДАКТИРОВАТЬ : поскольку некоторые из вас попросили факты (и мне не понравились те, что я уже предоставил) по моей эффективности выше, они вытекают из нескольких вещей:

  • Для Concat с использованием нулевых завершенных строк требуется сложность времени O (n + m). Для префикса длины часто требуется только O (m).
  • Длина, использующая нуль-терминированные строки, требует O (n) временной сложности. Префикс длины - O (1).
  • Длина и concat являются наиболее распространенными строковыми операциями. Существует несколько случаев, когда нулевые завершаемые строки могут быть более эффективными, но они встречаются гораздо реже.

Из приведенных ниже ответов, это некоторые случаи, когда строки с нулевым завершением являются более эффективными:

  • Когда вам нужно отключить начало строки и передать ее некоторому методу. Вы не можете сделать это в постоянное время с префиксом длины, даже если вам разрешено уничтожить исходную строку, потому что префикс длины, вероятно, должен следовать правилам выравнивания.
  • В некоторых случаях, когда вы просто перебираете символ строки по символу, вы можете сохранить регистр CPU. Обратите внимание, что это работает только в том случае, если вы не динамически выделили строку (потому что тогда вам придется освободить ее, что потребовало бы, чтобы вы сохранили этот регистр процессора, чтобы сохранить указатель, который вы изначально получили от malloc и друзей).

Ни одно из приведенных выше почти не распространено как длина и конкат.

В ответах ниже сказано еще одно:

  • Вам нужно отрезать конец строки

но это неверно - это одинаковое количество времени для строк с нулевым завершающим и длинным префиксом. (Строки с нулевым завершающим строем просто вставляют нуль, где вы хотите, чтобы новый конец был, префиксы длины просто вычитают из префикса.)


C не содержит строку как часть языка. «Строка» в C - это просто указатель на char. Так что, возможно, вы задаете неправильный вопрос.

«В чем причина отказа от строкового типа» может быть более актуальным. Для этого я хотел бы указать, что C не является объектно-ориентированным языком и имеет только базовые типы значений. Строка представляет собой концепцию более высокого уровня, которая должна быть реализована путем объединения значений других типов. C находится на более низком уровне абстракции.

в свете бушующего шквала ниже:

Я просто хочу указать, что я не пытаюсь сказать, что это глупый или плохой вопрос, или что способ представления строк - это лучший выбор. Я пытаюсь уточнить, что вопрос будет более лаконичным, если учесть тот факт, что C не имеет механизма для дифференциации строки как типа данных из массива байтов. Это лучший выбор в свете обработки и памяти сегодняшних компьютеров? Возможно нет. Но задним числом всегда 20/20 и все такое :)


Вопрос задается как строка с префиксом длины Length Prefixed Strings (LPS) отношению к zero terminated strings (SZ) , но в основном раскрывает преимущества префиксных строк длины. Это может показаться ошеломляющим, но, честно говоря, мы также должны учитывать недостатки LPS и преимущества SZ.

Насколько я понимаю, вопрос может быть даже понят как предвзятый способ спросить: «В чем преимущества Zero Terminated Strings?».

Преимущества (я вижу) строк с нулевым завершением:

  • очень просто, не нужно вводить новые понятия в язык, char arrays / char указатели могут делать.
  • основной язык просто включает минимальный синтаксический сахар, чтобы преобразовать что-то между двойными кавычками в кучу символов (на самом деле кучу байтов). В некоторых случаях его можно использовать для инициализации вещей, совершенно не связанных с текстом. Например, формат файла изображения xpm является допустимым источником C, который содержит данные изображения, закодированные в виде строки.
  • кстати, вы можете поместить нуль в строковый литерал, компилятор просто добавит еще один в конце литерала: "this\0is\0valid\0C" . Это строка? или четыре строки? Или пучок байтов ...
  • плоская реализация, отсутствие скрытой косвенности, отсутствие скрытого целого.
  • нет скрытого распределения памяти (ну, некоторые печально известные нестандартные функции, такие как strdup, выполняют распределение, но это в основном источник проблемы).
  • нет конкретной проблемы для небольшого или большого аппаратного обеспечения (представьте себе, что для управления 32-битной длиной префикса на 8-битных микроконтроллерах или ограничениями ограничения размера строки до 256 байт, это была проблема, с которой я действительно сталкивался с Turbo Pascal eons назад).
  • реализация строковых манипуляций - всего лишь несколько очень простых функций библиотеки
  • эффективный для основного использования строк: постоянный текст читается последовательно с известного старта (в основном сообщения пользователю).
  • нулевой завершающий нуль даже не является обязательным, доступны все необходимые инструменты для управления символами, такими как куча байтов. При выполнении инициализации массива в C вы можете даже избежать терминатора NUL. Просто установите нужный размер. char a[3] = "foo"; является действительным C (не C ++) и не ставит конечный ноль в a.
  • согласованный с точкой unix «все файл», включая «файлы», которые не имеют внутренней длины, например stdin, stdout. Вы должны помнить, что открытые примитивы чтения и записи реализованы на очень низком уровне. Это не вызовы библиотеки, а системные вызовы. И тот же API используется для двоичных или текстовых файлов. Первичные примитивы чтения файлов получают адрес и размер буфера и возвращают новый размер. И вы можете использовать строки в качестве буфера для записи. Использование другого типа строкового представления подразумевало бы, что вы не можете легко использовать литеральную строку в качестве буфера для вывода, иначе вам придется иметь очень странное поведение при передаче его в char* . А именно, не возвращать адрес строки, а вместо этого возвращать фактические данные.
  • очень легко манипулировать текстовыми данными, считываемыми из файла на месте, без бесполезной копии буфера, просто вставляйте нули в нужные места (ну, на самом деле, не с современным C, так как строки с двойными кавычками являются массивами const char, которые в настоящее время обычно хранятся в неизменяемых данных сегмент).
  • добавление некоторых значений int любого размера подразумевает проблемы выравнивания. Начальная длина должна быть выровнена, но нет причин делать это для данных символов (и, опять же, принудительное выравнивание строк будет означать проблемы при обработке их как кучу байтов).
  • длина известна во время компиляции для постоянных литеральных строк (sizeof). Итак, почему кто-то захочет сохранить его в памяти, добавив его к фактическим данным?
  • таким образом, что C делает (почти) всех остальных, строки рассматриваются как массивы char. Поскольку длина массива не управляется C, логическая длина не управляется ни для строк. Единственное, что удивительно, это то, что 0 добавлен в конце, но это только на уровне основного языка при вводе строки между двойными кавычками. Пользователи могут прекрасно вызывать функции манипуляции строкой, проходящие по длине, или даже использовать вместо них простое замещение. SZ - всего лишь объект. В большинстве других языков длина массива управляется, логично, что для строк является одинаковым.
  • в наше время все равно 1 байтовый набор символов недостаточно, и вам часто приходится иметь дело с закодированными строками Unicode, где количество символов сильно отличается от числа байтов. Это означает, что пользователи, вероятно, захотят больше, чем «просто размер», но также и другие сведения. Сохраняя длину, не используйте ничего (особенно естественное место для их хранения) в отношении этих других полезных фрагментов информации.

Тем не менее, нет необходимости жаловаться в редком случае, когда стандартные строки C действительно неэффективны. Доступны либы. Если бы я следовал этой тенденции, я должен был бы пожаловаться, что стандарт C не включает никаких функций поддержки регулярных выражений ... но действительно все знают, что это не настоящая проблема, поскольку для этой цели существуют библиотеки. Поэтому, когда требуется эффективная манипуляция строкой, почему бы не использовать библиотеку типа bstring ? Или даже строки C ++?

EDIT : Недавно я посмотрел на строки D. Достаточно интересно видеть, что выбранное решение не является ни префиксом размера, ни нулевым завершением. Как и в C, литеральные строки, заключенные в двойные кавычки, являются просто короткой рукой для неизменяемых массивов символов, а язык также имеет ключевое слово string, которое означает (неизменный массив символов).

Но массивы D намного богаче C-массивов. В случае статических массивов длина известна во время выполнения, поэтому нет необходимости хранить длину. У компилятора есть его во время компиляции. В случае динамических массивов длина доступна, но в документации D не указано, где она хранится. Насколько нам известно, компилятор мог бы сохранить его в каком-либо регистре или в некоторой переменной, хранящейся далеко от данных символов.

В нормальных массивах символов или в нелистных строках нет конечного нуля, поэтому программист должен сам поставить его, если он хочет вызвать некоторую функцию C из D. В частном случае литеральных строк, однако компилятор D все еще ставит ноль на конец каждой строки (чтобы упростить приведение к строкам C, чтобы упростить вызов функции C?), но этот ноль не является частью строки (D не учитывает ее в размере строки).

Единственное, что меня несколько разочаровывало в том, что строки должны быть utf-8, но длина, по-видимому, все еще возвращает несколько байтов (по крайней мере, это правда в моем компиляторе gdc) даже при использовании многобайтовых символов. Мне непонятно, является ли это ошибкой компилятора или по назначению. (ОК, я, наверное, выяснил, что произошло. Чтобы сказать компилятору D, что ваш источник использует utf-8, вы должны сначала поместить некоторый глупый порядок байтов. Я пишу глупо, потому что знаю, что не редактор делает это, особенно для UTF- 8, который должен быть совместим с ASCII).


Из уст лошади

Ни один из BCPL, B или C не поддерживает символьные данные на языке; каждый рассматривает строки, подобные векторам целых чисел, и дополняет общие правила несколькими условностями. В BCPL и B строковый литерал обозначает адрес статической области, инициализированной символами строки, упакованными в ячейки. В BCPL первый упакованный байт содержит количество символов в строке; в B нет счета, и строки заканчиваются специальным символом, который B записал *e . Это изменение было сделано частично, чтобы избежать ограничения длины строки, вызванного удержанием счетчика в 8- или 9-битном слоте, а отчасти потому, что поддержание счета казалось, по нашему опыту, менее удобным, чем использование терминатора.

Деннис М Ричи, разработка языка C


Я думаю, у него есть исторические причины и нашел это в Википедии :

В то время, когда были разработаны C (и языки, из которых он был получен), память была чрезвычайно ограниченной, поэтому использование одного байта накладных расходов для хранения длины строки было привлекательным. Единственная популярная альтернатива в то время, обычно называемая «строкой Pascal» (хотя также использовалась ранними версиями BASIC), использовала ведущий байт для хранения длины строки. Это позволяет строке содержать NUL и для нахождения длины требуется только один доступ к памяти (время O (1) (постоянное)). Но один байт ограничивает длину до 255. Это ограничение по длине было гораздо более ограничительным, чем проблемы с строкой C, так что строка C вообще выиграла.


Не обязательно Обоснование, но контрапункт к закодированному по длине

  1. Определенные формы кодирования с динамической длиной превосходят кодировку статической длины, что касается памяти, все зависит от использования. Просто посмотрите на UTF-8 для доказательства. Это по существу расширяемый массив символов для кодирования одного символа. Для каждого расширенного байта используется один бит. Окончание NUL использует 8 бит. Длина-префикс, я думаю, можно условно назвать бесконечной длиной, используя 64 бита. Как часто вы попадаете в случай ваших дополнительных бит, это решающий фактор. Только 1 очень большая строка? Кого волнует, если вы используете 8 или 64 бит? Много маленьких струн (т.е. струны английских слов)? Тогда ваши префиксные расходы составляют большой процент.

  2. Строки с префиксом длины, позволяющие экономить время, не являются реальностью . Независимо от того, требуется ли вам предоставленная информация для обеспечения длины, вы рассчитываете на время компиляции или действительно получаете динамические данные, которые вы должны кодировать в виде строки. Эти размеры вычисляются в какой-то момент в алгоритме. Может быть предоставлена отдельная переменная для хранения размера строки с нулевым завершением . Это делает сравнение по экономии времени спорным. У одного просто есть дополнительный NUL в конце ... но если длина кодирования не включает в себя NUL, тогда буквально нет разницы между ними. Алгоритмических изменений вообще не требуется. Просто предварительный проход, который вы должны вручную создать самостоятельно, вместо того, чтобы иметь компилятор / время выполнения, сделайте это за вас. C в основном о том, чтобы делать вещи вручную.

  3. Префикс длины, являющийся необязательным, является точкой продажи. Я не всегда нуждаюсь в дополнительной информации для алгоритма, поэтому, чтобы сделать это для каждой строки, мое предсказанное время + вычисление никогда не может опускаться ниже O (n). (Т.е. генератор случайных чисел аппаратного обеспечения 1-128.Я могу вытащить из «бесконечной строки». Предположим, что он генерирует только так быстро, поэтому наша длина строки меняется все время. Но мое использование данных, вероятно, не волнует, как много случайных байтов, которые у меня есть.Он просто хочет следующий доступный неиспользуемый байт, как только он сможет получить его после запроса.Я мог ждать на устройстве.Но я мог бы также иметь буфер предварительно прочитанных символов. ненужная трата вычислений. Нулевая проверка более эффективна.)

  4. Префикс длины - хорошая защита от переполнения буфера? Это разумное использование библиотечных функций и их реализация. Что делать, если я получаю неверные данные? Мой буфер имеет длину 2 байта, но я говорю, что это 7! Пример: Если метод gets () предназначался для использования в известных данных, он мог иметь внутреннюю проверку буфера, которая проверила скомпилированные буферы и malloc ()звонит и продолжает следовать спецификации. Если бы это предназначалось для использования в качестве канала для неизвестного STDIN, чтобы добраться до неизвестного буфера, то, очевидно, нельзя знать, что он соответствует размеру буфера, что означает, что аргумент длины бессмыслен, вам нужно что-то еще здесь, как канарейка. В этом случае вы не можете префикс длины и потоки, вы просто не можете. Это означает, что проверка длины должна быть встроена в алгоритм, а не волшебная часть системы ввода. TL; DR NUL-terminated никогда не должен был быть небезопасным, это просто закончилось тем путем злоупотребления.

  5. встречная точка: NUL-прерывание раздражает двоичный код. Вам нужно либо сделать префикс длины, либо каким-то образом преобразовать NUL-байты: escape-коды, переназначение диапазона и т. Д., Что, конечно же, означает использование большей памяти / уменьшенная информация / больше операций за каждый байт. Префикс длины в основном выигрывает здесь войну. Единственный недостаток преобразования заключается в том, что никакие дополнительные функции не должны записываться для покрытия строк префикса длины. Это означает, что на ваших более оптимизированных под-O (n) подпрограммах вы можете автоматически использовать их O (n) эквиваленты, не добавляя больше кода. Недостатком является, конечно же, время / память / компрессионный отход при использовании на тяжелых строках NUL.В зависимости от того, сколько вашей библиотеки вы дублируете для работы с двоичными данными, может иметь смысл работать исключительно с строками префикса длины. Тем не менее, можно было бы также сделать то же самое с строками префикса длины ... -1 длина могла означать NUL-terminated, и вы могли бы использовать строки с нулевым завершением внутри длины.

  6. Concat: «O (n + m) vs O (m)« Я предполагаю, что вы ссылаетесь на m как на общую длину строки после конкатенации, потому что оба они должны иметь минимальное количество операций (вы не можете просто придерживаться -в строке 1, что делать, если вам нужно перераспределить?). И я предполагаю, что n - это мифическое количество операций, которые вам больше не нужно делать из-за предварительного вычисления. Если это так, тогда ответ прост: предварительно вычислите. Есливы настаиваете на том, что у вас всегда будет достаточно памяти, чтобы не требовать перераспределения, и это основа нотации большого О, тогда ответ еще более прост: выполните двоичный поиск в выделенной памяти для конца строки 1, очевидно, что есть большой образец бесконечных нулей после строки 1, чтобы мы не беспокоились о realloc. Там легко получить n до log (n), и я едва попробовал. Который, если вы вспомните log (n), по существу, всегда велик до 64 на реальном компьютере, что по существу напоминает выражение O (64 + m), которое по существу равно O (m). (И да, эта логика использовалась во время анализа реальных структур данных, используемых в настоящее время. Это не дерьмо с моей головы).

  7. Concat () / Len () снова : запомните результаты. Легко. Превращает все вычисления в предварительные вычисления, если это возможно / необходимо. Это алгоритмическое решение. Это не принудительное ограничение языка.

  8. Прохождение суффикса строк проще / возможно с завершением NUL. В зависимости от того, как префикс длины реализован, он может быть разрушительным в исходной строке и иногда даже невозможен. Требуется копия и передать O (n) вместо O (1).

  9. Аргумент-прохождение / де-референция меньше для префикса NUL-конца и длины. Очевидно, потому что вы передаете меньше информации. Если вам не нужна длина, это экономит много места и позволяет оптимизировать.

  10. Вы можете обмануть. Это действительно просто указатель. Кто сказал, что вы должны прочитать это как строку? Что делать, если вы хотите прочитать его как отдельный символ или поплавок? Что делать, если вы хотите сделать обратное и прочитать float как строку? Если вы будете осторожны, вы можете сделать это с помощью NUL-завершения. Вы не можете сделать это с префиксом длины, это тип данных, явно отличный от указателя. Скорее всего, вам нужно будет построить строку побайтно и получить длину. Конечно, если вы хотите что-то вроде целого поплавка (возможно, в нем есть NUL), вам все равно придется читать байты по-байтам, но детали вам решать.

TL; DR Используете ли вы двоичные данные? Если нет, то NUL-окончание допускает большую алгоритмическую свободу. Если да, то ваша основная проблема связана с количеством кода и скоростью / памятью / сжатием. Совпадение двух подходов или воспоминаний может быть лучше.


gcc принимает коды ниже:

char s [4] = "abcd";

и это нормально, если мы рассматриваем это как массив символов, но не строку. То есть мы можем получить к нему доступ с помощью s [0], s [1], s [2] и s [3] или даже с memcpy (dest, s, 4). Но мы будем получать беспорядочные символы, когда пытаемся с puts (s), или хуже с strcpy (dest, s).


Как-то я понял, что вопрос подразумевает отсутствие поддержки компилятора строк с префиксом длины в C. В следующем примере показано, по крайней мере, вы можете запустить свою собственную библиотеку строк C, где длины строк подсчитываются во время компиляции, с такой конструкцией:

#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;
}

Это, однако, не будет иметь проблем, так как вам нужно быть осторожным, когда нужно специально освободить этот указатель на строку и когда он статически выделен ( charмассив литералов ).

Изменить: как более прямой ответ на этот вопрос, мой взгляд заключается в том, что C мог поддерживать как имеющую длину строки (как постоянную времени компиляции), если она вам нужна, но все же без лишних затрат памяти, если вы хотите использовать только указатели и нулевое завершение.

Конечно, похоже, что работа с нулевыми строками была рекомендуемой практикой, поскольку стандартная библиотека вообще не принимает длину строки в качестве аргументов, а так как извлечение длины не является таким простым кодом, как char * s = "abc", как показывает мой пример.


Во многих отношениях С был примитивным. И мне это понравилось.

Это был шаг выше уровня ассемблера, что дало вам почти такую ​​же производительность с языком, который было намного проще писать и поддерживать.

Нулевой терминатор прост и не требует специальной поддержки языка.

Оглядываясь назад, кажется, что это не удобно. Но я использовал ассемблерный язык еще в 80-х годах, и в то время он казался очень удобным. Я просто думаю, что программное обеспечение постоянно развивается, и платформы и инструменты постоянно становятся все более изощренными.


Многие проектные решения, связанные с 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 может иметь кодированные длины строк, подходы, которые были изобретены до того времени, будут иметь все необходимые функции, которые должны иметь возможность работать с частью строки, чтобы принять базовый адрес строки и желаемый индекс как два отдельных параметра. Использование обхода нулевого байта позволило избежать этого требования. Хотя другие современные методы были бы лучше с сегодняшними машинами (современные компиляторы часто передают параметры в регистрах, а memcpy можно оптимизировать способами, которые не могут быть реализованы с помощью strcpy () - эквивалентов). В достаточном производственном коде используются строки с нулевым байтом, которые трудно изменить ни на что другое.

PS - В обмен на небольшое ограничение скорости на некоторые операции и крошечный бит дополнительных накладных расходов на более длинных строках, было бы возможно иметь методы, которые работают со строками, принимают указатели непосредственно к строкам, проверенным границам буферам строк или структуры данных, идентифицирующие подстроки другой строки. Функция типа «strcat» выглядела бы как [современный синтаксис]

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);
  }
}

Немного больше, чем метод K & R strcat, но он будет поддерживать проверку границ, которой не существует метод K & R. Кроме того, в отличие от текущего метода, можно было бы легко конкатенировать произвольную подстроку, например

/* 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);
}

Обратите внимание, что время жизни строки, возвращаемой temp_substring, будет ограничено символом sи src, что когда-либо было короче (поэтому метод infдолжен быть передан, если он был локальным, он будет умирать при возврате метода).

С точки зрения стоимости памяти, строки и буферы до 64 байтов имели бы один байт служебных данных (так же, как строки с нулевым завершением); более длинные строки будут иметь немного больше (независимо от того, разрешено ли количество накладных расходов между двумя байтами и максимально необходимым, это компромисс между временем / пространством). Специальное значение байта длины / режима будет использоваться, чтобы указать, что строковой функции была предоставлена ​​структура, содержащая байт-указатель, указатель и длину буфера (которые затем могут произвольно индексироваться в любую другую строку).

Конечно, K & R не реализовал такую ​​вещь, но это, скорее всего, потому, что они не хотели тратить много усилий на обработку строк - область, где даже сегодня многие языки кажутся довольно анемичными.


По словам Джоэла Спольского в этом блоге ,

Это связано с тем, что микропроцессор PDP-7, на котором был изобретен язык программирования UNIX и C, имел тип строки ASCIZ. ASCIZ означало «ASCII с Z (ноль) в конце».

Увидев все остальные ответы здесь, я убежден, что даже если это так, это лишь часть причины, когда C имеет «строки» с нулевым завершением. Этот пост достаточно освещает, как простые вещи, такие как струны, могут быть довольно сложными.


Предполагая на мгновение, что C реализованные строки, путь Pascal, путем префикса их по длине: представляет собой длинную строку длиной 7 символов, такую ​​же ТИП ДАННЫХ, как 3-char string? Если да, то какой код должен генерировать компилятор, когда я назначаю первое последнему? Должна ли строка быть усечена или автоматически изменяться? Если изменить размер, следует ли защищать эту операцию блокировкой, чтобы сделать ее безопасной? Сторона подхода C сделала все эти проблемы, вроде этого или нет :)





null-terminated