LINUX.ORG.RU

Нелогичность в stdlib

 


0

2

Почему функции

char *strpbrk(const char *s, const char *accept);
char *strstr(const char *haystack, const char *needle);
char *strcasestr(const char *haystack, const char *needle); // GNU
char *strchr(const char *s, int c);
char *strrchr(const char *s, int c);
Возвращают char вместо const char? Может я упускаю какой-то сакральный смысл? Просто преобразование `return (char *)s;`, нарушает все допустимые нормы программирования (MISRA, NASA JPL, etc). Да и просто по логике - взяли константную строку на вход, на выходе позволяем делать с ней все, что угодно.

★★★★

Мы на вход можем подавать и неконстантную строку...

fsb4000 ★★★★★ ()
Ответ на: комментарий от xaizek

Это решается через две версии функции с постфиксом и без. Я просто не вижу причины, почему этого не сделано.

PPP328 ★★★★ ()
Ответ на: комментарий от PPP328

Чтобы не дублировать API для константного и неконстантного аргумента и сделано.

xaizek ★★★★★ ()
Последнее исправление: xaizek (всего исправлений: 1)

Может это контракт, не думал? Контракт на то, что функция не будет менять внутри себя ту строку, что объявлена как const char*

Deleted ()
Ответ на: комментарий от Deleted

Такое построение API делает неотслеживаемым синтаксически такой код:

const char my[] = "abcdef";
char ts[] = "ab";

char * res = strstr(my, ts);
res[2] = 'n';

Не, ну а чо, синтаксически все верно, отследить что ты меняешь константную строку нельзя.

PPP328 ★★★★ ()

Если ты хочешь чтобы тебя били по рукам, когда ты подсунул const char* в такие функции, а присваиваешь переменной char*, то компилируй C код C++ компилятором.

#include <cstring>

int main()
{
    const char *str = "Try not. Do, or do not. There is no try.";
    char target = 'T';
    char* result = std::strchr(str, target);
}
main.cpp error: invalid conversion from ‘const char*’ to ‘char*’ [-fpermissive]
     char* result = std::strchr(str, target);
<встроенное>: ошибка выполнения рецепта для цели «src/main.o»
make: *** [src/main.o] Ошибка 1
gcc version 7.2.0 (Ubuntu 7.2.0-1ubuntu1~16.04) 
fsb4000 ★★★★★ ()
Ответ на: комментарий от PPP328

Изменил

#include <cstring>
на
#include <string.h>

и

std::strchr

на

strchr

Ошибка при компиляции осталась таже.

fsb4000 ★★★★★ ()

взяли константную строку на вход,

Именно. Указанные функции строки не меняют.

на выходе позволяем делать с ней все, что угодно.

Конечно. Ибо менять или не менять далее — решать программисту, а не вышевызванной функции. Она закончилась. Можно ли менять память по возвращенному смещению от переданного аргумента она не знает. И слава богу.

vodz ★★★★★ ()

cv-квалификаторы не особо консистентные - поздняя нашлепка же.

anonymous ()

const в Си - скорее вопрос соглашения, всё на совести программиста. А дублировать функции ради того, чтобы всё было как в плюсах - да ну нафиг.

meliafaro ★★★★★ ()

Да и просто по логике - взяли константную строку на вход, на выходе позволяем делать с ней все, что угодно.

а как по вашему, зачем ЭТИ функции возвращают «char *» ?? для чего они служат и зачем нужны. Директивно вернуть из них (еретичный для C) const - просто убить их функционал и цель существования.

MKuznetsov ★★★★★ ()
Ответ на: комментарий от MKuznetsov

Кто мешает возвращать в таких случаях не char * а i32? Половина функций из той же stdlib так и делают:

       size_t strspn(const char *s, const char *accept);
       size_t strcspn(const char *s, const char *reject);

Кто мешал сделать так:

      int strstr(const char *haystack, const char *needle);

char * found = hay + strstr(hay, ndl);
?

Тогда никаких претензий не было бы. И stdlib заодно стал бы соответсвовать стандартам безопасности:

All libraries used in production code shall be written to comply
with the provisions of this document, and shall have been subject
to appropriate validation.
                             [IEC 61508 Part 3]

PPP328 ★★★★ ()
Ответ на: комментарий от PPP328

Где я в коде выше использовал size_t?

Во-первых, в ваших «в половине». Во-вторых, size_t на 33 бита длиннее, чем int в 64 мире. В-третьих, да даже (int)-1 то ещё угрёбище.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Во-вторых, size_t на 33 бита длиннее, чем int в 64 мире

Я повторюсь, есть такая вещь как ssize_t.

-1 как код возврата в случае есть функция чего-то не нашла - вполне разумное решение, так как любая си-функция работы со строками, которая возвращает char * требует проверки на NULL. В случае возврата ssize_t эта проверка просто изменится на if (... < 0)

PPP328 ★★★★ ()
Ответ на: комментарий от PPP328

Я повторюсь, есть такая вещь как ssize_t.

Этот тип появился не от хорошей жизни. Он применяется для костыльного API read/write, так как 50 лет назад никто не задумывался, что 2 Gb файл это не так уж и много, потом согласились, что типа (4Gb - errno) это тоже нормальный такой предел. Короче, если б сразу был бы аргумент для ошибки у read/write, то типа ssize_t вообще бы не было. Для строк этот тип вообще не юзается.

оторая возвращает char * требует проверки на NULL.

NULL хорош прежде всего тем, что это недопустимое значение для использования. Потому можно передавать скажем во free(NULL) и так далее. В вашем же API придётся передавать два аргумента в более глубокие функции: prt и ssize_t, потом проверять на -1. Это - УРОДСТВО.

vodz ★★★★★ ()
Последнее исправление: vodz (всего исправлений: 1)
Ответ на: комментарий от vodz

NULL хорош прежде всего тем, что это недопустимое значение для использования.

На который ВСЕГДА нужно проверять (кроме исключения в free, но оно там тоже не сразу появилось). ВСЕ остальные функции не могут нормально обработать NULL.

PPP328 ★★★★ ()
Ответ на: комментарий от PPP328

В случае возврата ssize_t эта проверка просто изменится на if (... < 0)

В случае < 0 ты делишь максимальное значение результата на два. Для большиства ситуаций не критично, но как для общего случае - кривизна.

anonymous ()
Ответ на: комментарий от anonymous

В случае < 0 ты делишь максимальное значение результата на два

О, ещё один срыватель покровов. Да мелочи это. Важно то, что ваша my_find_str должна передавать в более глубокие функции уже не только ptr, когда он NULL=«не нашла» или валидное смещение, а ssize_t и ptr, ибо prt-1 - это валидный адрес в 99.(9)% случаях.

vodz ★★★★★ ()
Ответ на: комментарий от PPP328

На который ВСЕГДА, кроме исключения в free...

Ничерта то вы ничего не поняли. free() тоже проверяет. Но именно об этом я и говорю, о API всех функций программы. Ибо free() проверяет только ptr, а не ptr + flag валидности.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Да мелочи это

Действительно, можно еще как в паскале для строк байтом обойтись. Да и тип size_t выкинуть как бесполезный, есть же int. Без подобных мелочей можно прожить.

Важно то, что ваша my_find_str должна передавать в более глубокие функции уже не только ptr, когда он NULL=«не нашла» или валидное смещение, а ssize_t и ptr, ибо prt-1 - это валидный адрес в 99.(9)% случаях.

Что за норкомания? my_find_str вернет только ssize_t (кстати нестандартный тип), а на месте вызова либо будет создан новый валидный указатель, либо NULL. Куда ты собрался передавать пару?

anonymous ()
Ответ на: комментарий от anonymous

Действительно, можно еще как в паскале для строк байтом обойтись. Да и тип size_t выкинуть как бесполезный, есть же int. Без подобных мелочей можно прожить.

Это вы со своим альтерэго разговаривали?

кстати нестандартный тип

Ну дык.

а на месте вызова либо будет создан новый валидный указатель, либо NULL

Охренеть удобство. То есть я уже не смогу вызвать my_show(my_find_str())?

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Это вы со своим альтерэго разговаривали?

С тобой. Это ж для тебя тип - мелочь.

То есть я уже не смогу вызвать my_show(my_find_str())?

А чо, в С уже принято нулевые указатели передавать куда попало? И забивать на проверки результатов функций?

anonymous ()
Ответ на: комментарий от anonymous

С тобой. Это ж для тебя тип - мелочь.

С историей типа ssize_t вы могли бы ознакомиться двумя постами ранее.

А чо, в С уже принято нулевые указатели передавать куда попало? И забивать на проверки результатов функций?

Да, именно так. my_show должна сказать: я ничего не нашла или я нашла вот это. Не верите? Ну сделайте man free

vodz ★★★★★ ()
Ответ на: комментарий от PPP328

Тебе контракт строки говорит, что он строку менять не будет а не не может.

Так проще?

Deleted ()
Ответ на: комментарий от vodz

С историей типа ssize_t вы могли бы ознакомиться двумя постами ранее.

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

Ну сделайте man free

Не шлангуй мне тут, все строковые функции С выпадут в осадок из-за NULL.

anonymous ()
Ответ на: комментарий от anonymous

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

Врать не надо. Я сказал, что по сравнению с уродованием API ваш старший бит - мелочи. Покажите данные в 63 бита размерности? А вот API предлагаете изуродовать сейчас.

Да и вообще. ТС на бегу поменял int на ssize_t, то есть таки вспомнил о потеряных аж 32 битах. А вы предлагаете, в лучших традициях модно-молодёжно «старики идиоты и надо всё сделать ровно по другому» уродливое API, оправдывая, что подумаешь, можно же -1 зарезервировать.

все строковые функции С выпадут в осадок из-за NULL.

И это хорошо. Ибо у нас тут не ява, хотите скорости — проверяйте там где надо руками, а не чтобы оно на каждом шаге спотыкалось вдруг чего.

А разговор был о my_* функциях.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Врать не надо. Я сказал, что по сравнению с уродованием API ваш старший бит - мелочи

Какой «мой» старший бит? Я за указатели.

Покажите данные в 63 бита размерности?

А кто тебе сказал, что в С size_t - 64 бита? Кто тебя так жестоко обманул?

вы предлагаете, в лучших традициях модно-молодёжно «старики идиоты и надо всё сделать ровно по другому»

Я вообще ничего не предлагаю, я против ssize_t.

Ибо у нас тут не ява, хотите скорости — проверяйте там где надо руками, а не чтобы оно на каждом шаге спотыкалось вдруг чего.

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

anonymous ()
Ответ на: комментарий от anonymous

А кто тебе сказал, что в С size_t - 64 бита?

Это я сказал, для примера. Могу себе позволить, ибо начинал с типичного тогда 35 лет назад 16 бит, а теперь типичное - 64. Хотя даже поигрался на AS400 с их 128...

забейте на str* функции

А, это вы Брут... Я подростковый инфантильный нигилизм не лечу, это не ко мне. Я могу и на ash/bash/awk/sed если можно написать, и mem*() тоже умею, возможно не хуже вашего.

vodz ★★★★★ ()
Ответ на: комментарий от vodz

Это я сказал, для примера. Могу себе позволить, ибо начинал с типичного тогда 35 лет назад 16 бит, а теперь типичное - 64. Хотя даже поигрался на AS400 с их 128...

И теперь ты решил отменить все что меньше 64 бит? Хотя да. ты же «можешь себе позволить».

Я подростковый инфантильный нигилизм не лечу, это не ко мне. Я могу и на ash/bash/awk/sed если можно написать, и mem*() тоже умею, возможно не хуже вашего.

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

anonymous ()

не пользуй это говно времен PDP-11. Пользуй <algorithm>. Он перекрывает всё это барахло по функционалу, типобезопасности, производительности, гибкости.

dzidzitop ★★ ()
Ответ на: комментарий от fsb4000

то компилируй C код C++ компилятором

Такой подход не сработает в общем случае, т.к. компилятор C++ не сможет распарсить любой C-код из-за несовместимости этих языков.

Sorcerer ★★★★★ ()
Ответ на: комментарий от Sorcerer

Верно, но в с++17 добавили немного совместимости с С. А в c++20 ещё добавят долгожданный Designated Initialization:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf

Так что ещё больше С кода будет компилироваться С++ компилятором...

Также у gcc есть полезное предупреждение:

-Wc++-compat

fsb4000 ★★★★★ ()
Ответ на: комментарий от fsb4000

ещё добавят долгожданный Designated Initialization:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0329r4.pdf

Example:
struct A { int x; int y; int z; };
A a{.y = 2, .x = 1}; // error; designator order does not match declaration order
A b{.x = 1, .z = 2}; // ok, b.y initialized to 0

Оно такое не нужно.

Sorcerer ★★★★★ ()
Ответ на: комментарий от Sorcerer

Нужно и логично. По-хорошему, списки инициализации надо было так же ограничить.

anonymous ()
Ответ на: комментарий от kawaii_neko

Переход на личности? Ок. Много. Из того, что с ходу вспомню:

  • библиотека ввода\вывода для байт-потоков RS-232. Используется в испытательных стендах пьезоэлектрических материалов в НКТБ «Пьезоприбор»
  • библиотека базовых типов для памяти-безопасных функций без зависимости от stdlib - строки, байты, конвертации, мьютексы, вот это все. Используется в софте, который сейчас установлен в 90% кинотеатров России и частично в СНГ (Казахстан, Белоруссия).
  • несколько библиотек анализа памяти
  • библиотека конвертации вложенных структур с бинарной поддержкой перловых nfreeze/thaw. Используется опять в кинотеатрах.
  • библиотека поддержки различных протоколов обмена (начиная от binary-modbus, заканчивая xml-over-ssl). Имеет самогенерируемый код по таблицам. Используется в кинотеатрах как часть ТМС.

Это те, которые активно используются в проде. Есть еще proof-of-concepts, just-for-fun проекты и пр. (например конвертации PNG в [ bb ])

PPP328 ★★★★ ()
Ответ на: комментарий от Sorcerer

Оно такое не нужно.

Все правильно делают. Порядок важен т.к. это вам не С, тут конструкторы могут отрабатывать. А инициализация по дефолту во-первых удобно, во-вторых безопасно. Совместимость с С она не ломает, а кому очень надо оптимизация - дернет нужные поля по отдельности.

anonymous ()
Ответ на: комментарий от anonymous

Может и правильно, только это не о совместимости с Си. В смысле, что сишный код в общем случае плюсами всё равно собираться не будет.

Sorcerer ★★★★★ ()
Последнее исправление: Sorcerer (всего исправлений: 1)
Ответ на: комментарий от Sorcerer

Может и правильно, только это не о совместимости с Си.

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

anonymous ()
Ответ на: комментарий от anonymous

Насчёт «порядок и так обычно сохраняют» - неочевидно. За порядком инициализации в этом случае обычно не следят, и это отличная фича языка, когда ты можешь поменять местами элементы структуры (чтобы они компактнее разместились, например), а на коде инициализации это никак не отразится.

Sorcerer ★★★★★ ()
Ответ на: комментарий от Sorcerer

За порядком инициализации в этом случае обычно не следят, и это отличная фича языка

Нет, как и многое в С, это не отличная, а отличительная черта языка. Забивать на типы, забивать на порядок, забивать на константность, забивать на управление ресурсами и пр.

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

А еще можно тип элемента поменять на несовместимый (int* на double*, например) и тоже «на коде инициализации это никак не отразится».

anonymous ()
Ответ на: комментарий от kawaii_neko

Поясню - иногда нет возможности использовать stdlib. Неважно почему - для микроконтроллера или какой-нибудь еще встроенки. Тогда пишется своя реализация (или покупается чужая (или берется жирный прежирный stdlib от glibc\etc, но он жирный\не обеспечивает что-то специфичное для процессора\платформы)).
Так вот. Вот это:

char *strstr(const char *haystack, const char *needle)
{
   <...>

   return (char *)hasystack [+ <...>];
   <...>
}
Сертификацию MISRA не пройдет. Как не пройдет сертификацию любой, кто такую либу будет юзать.

PPP328 ★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.