В настоящее время я работаю над библиотекой, написанной на C. Многие функции этой библиотеки ожидают строку как char*
или const char*
в своих аргументах. Я начал с тех функций, которые всегда ожидали длину строки как size_t
так, чтобы нулевое завершение не требовалось. Однако при написании тестов это приводило к частому использованию strlen()
, например:
const char* string = "Ugh, strlen is tedious";
libFunction(string, strlen(string));
Доверие пользователя передать правильно завершенные строки приведет к менее безопасному, но более лаконичному и (на мой взгляд) читаемому коду:
libFunction("I hope there's a null-terminator there!");
Итак, что толковая практика здесь? Сделать API более сложным в использовании, но заставить пользователя задуматься о вводе или задокументировать требование для строки с нулевым символом в конце и доверить вызывающей стороне?
CreateFile
принимаетLPTCSTR lpFileName
параметр в качестве входных данных. Длина строки не ожидается от вызывающей стороны. Фактически, использование NUL-завершенных строк настолько укоренилось, что в документации даже не упоминается, что имя файла должно заканчиваться NUL-символами (но, конечно, это должно быть).LPSTR
тип говорит, что строки могут заканчиваться NUL, и если нет , то это будет указано в соответствующей спецификации. Таким образом, если специально не указано иное, такие строки в Win32, как ожидается, будут заканчиваться NUL.StringCbCat
например, только у места назначения есть максимальный буфер, что имеет смысл. Источник по - прежнему обычный NUL с концевым C строка. Возможно, вы могли бы улучшить свой ответ, уточнив разницу между входным параметром и выходным параметром. Выходные параметры всегда должны иметь максимальную длину буфера; входные параметры обычно заканчиваются NUL (есть исключения, но по моему опыту редкие).В Си идиома состоит в том, что символьные строки заканчиваются NUL, поэтому имеет смысл придерживаться общепринятой практики - на самом деле относительно маловероятно, что пользователи библиотеки будут иметь строки, не заканчивающиеся NUL (поскольку для их печати требуется дополнительная работа использование printf и использование в другом контексте). Использование любого другого типа строки неестественно и, вероятно, относительно редко.
Кроме того, в данных обстоятельствах ваше тестирование кажется мне немного странным, поскольку для правильной работы (с использованием strlen) вы предполагаете строку с NUL-оканчивающейся строкой. Вам следует протестировать случай строк, не заканчивающихся NUL, если вы намерены работать с ними в своей библиотеке.
источник
Ваш аргумент "безопасности" на самом деле не верен. Если вы не доверяете пользователю вручать вам строку с нулевым символом в конце, когда это то, что вы задокументировали (и что является «нормой» для простого C), вы не можете доверять длине, которую они вам дают (которую они вероятно, используйте,
strlen
как вы делаете, если у них нет этого под рукой, и что не получится, если «строка» изначально не была строкой).Однако существуют веские причины требовать длину: если вы хотите, чтобы ваши функции работали с подстроками, возможно, намного проще (и эффективнее) передать длину, чем заставить пользователя выполнить какое-либо копирование назад и вперед, чтобы получить нулевой байт. в правильном месте (и рискуйте ошибками по пути).
Возможность обрабатывать кодировки, в которых нулевые байты не являются окончаниями, или возможность обрабатывать строки, которые имеют встроенные нулевые значения (специально), может быть полезна в некоторых обстоятельствах (зависит от того, что именно выполняют ваши функции).
Возможность обрабатывать ненулевые данные (массивы фиксированной длины) также удобна.
Короче говоря: зависит от того, что вы делаете в своей библиотеке, и какой тип данных вы ожидаете, что ваши пользователи будут обрабатывать.
Там также возможно аспект производительности. Если вашей функции нужно заранее знать длину строки, и вы ожидаете, что ваши пользователи, по крайней мере, обычно уже знают эту информацию, то, что они передадут ее (а не вычислят), это может сократить несколько циклов.
Но если ваша библиотека ожидает обычные простые текстовые строки ASCII и у вас нет мучительных ограничений производительности и очень хорошего понимания того, как ваши пользователи будут взаимодействовать с вашей библиотекой, добавление параметра длины не будет хорошей идеей. Если строка не завершена должным образом, скорее всего, параметр длины будет таким же поддельным. Я не думаю, что вы получите много с этим.
источник
Нет. По определению строки всегда заканчиваются нулем, длина строки избыточна.
Символьные данные с ненулевым символом в конце никогда не должны называться «строкой». Обработка этого (и отбрасывание длины) обычно должна быть заключена в библиотеку, а не часть API. Требование длины в качестве параметра просто для того, чтобы избежать одиночных вызовов strlen (), вероятно, является преждевременной оптимизацией.
Доверие к вызывающей функции API не является небезопасным ; неопределенное поведение вполне нормально, если задокументированные предварительные условия не выполняются.
Конечно, хорошо разработанный API не должен содержать подводных камней и должен облегчать правильное использование. И это просто означает, что это должно быть как можно более простым и понятным, избегая избыточностей и следуя языковым соглашениям.
источник
Вы должны всегда держать свою длину вокруг. Например, ваши пользователи могут захотеть содержать в них значения NULL. И во-вторых, не забывайте, что
strlen
это O (N) и требует касания всего кеша пока. И в-третьих, это облегчает передачу подмножеств - например, они могут дать меньше, чем фактическая длина.источник
strlen
в циклическом тесте.)Вы должны различать передачу строки и буфер .
В Си строки традиционно заканчиваются NUL. Вполне разумно ожидать этого. Поэтому обычно нет необходимости передавать длину строки; это может быть вычислено
strlen
при необходимости.При обходе буфера , особенно того, в который записывается, вы обязательно должны передать размер буфера. Для целевого буфера это позволяет вызываемому пользователю убедиться, что он не переполняет буфер. Для входного буфера это позволяет вызываемому пользователю избежать чтения после конца, особенно если входной буфер содержит произвольные данные, происходящие из ненадежного источника.
Возможно, есть некоторая путаница, потому что могут быть как строки, так и буферы,
char*
а также потому, что многие строковые функции генерируют новые строки, записывая в целевые буферы. Некоторые люди тогда приходят к выводу, что строковые функции должны иметь длину строки. Однако это неточный вывод. Практика включения размера с буфером (будет ли этот буфер использоваться для строк, массивов целых чисел, структур и т. Д.) Является более полезной и более общей мантрой.(В случае чтения строки из ненадежного источника (например, сетевого сокета) важно указать длину, так как вход может не заканчиваться NUL. Однако вы не должны рассматривать ввод как строку. следует рассматривать его как произвольный буфер данных, который может содержать строку (но вы не знаете, пока вы на самом деле не подтвердите ее), поэтому это все равно следует принципу, что буферы должны иметь ассоциированные размеры, и что строки не нуждаются в них.)
источник
Если функции в основном используются со строковыми литералами, боль при работе с явными длинами может быть минимизирована путем определения некоторых макросов. Например, учитывая функцию API:
можно определить макрос:
и затем вызовите это как показано в:
Хотя может быть возможно придумать «творческие» вещи для передачи этого макроса, который будет компилироваться, но на самом деле не будет работать, использование
""
по обе стороны строки в оценке «sizeof» должно отлавливать случайные попытки использовать символ указатели, отличные от разложенных строковых литералов [при их отсутствии""
попытка передать указатель на символ ошибочно даст длину в качестве размера указателя, минус один.Альтернативный подход в C99 будет определять тип структуры «указатель и длина» и определять макрос, который преобразует строковый литерал в составной литерал этого структурного типа. Например:
Обратите внимание, что если кто-то использует такой подход, он должен передавать такие структуры по значению, а не передавать их адреса. В противном случае что-то вроде:
может потерпеть неудачу, так как время жизни составных литералов закончится в конце их вложенных операторов.
источник