С++: фичи, трюки и ловушки

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 14878
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

С++: фичи, трюки и ловушки

Непрочитанное сообщение Olej » 02 июн 2021, 20:27

Известное дело, что C++ string конструируются из C char* простым string(s), или даже автоматическим вызовом конструктора...

А вот вопрос, если вы перед вызовом string(s) не проверяете char* s на равенство NULL?

Проверяем:

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/strnull$ cat nullstr.cc 
#include <iostream>
#include <string>

int main()
{
    const char* n = NULL;
    std::string sn(n);
    std::cout << sn << std::endl;
 
}

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/strnull$ g++ -Wall -pedantic -std=c++2a nullstr.cc -o nullstr

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/strnull$ ./nullstr 
terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_M_construct null not valid
Аварийный останов (стек памяти сброшен на диск)
Вот так!
Проверяйте char* на NULL! :lol:
Вложения
nullstr.cc
(139 байт) 9 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 14878
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: С++ в относительно новых стандартах

Непрочитанное сообщение Olej » 03 июн 2021, 19:50

Olej писал(а):
02 июн 2021, 20:27
Ещё более разительный пример (т.е. такой, что возникни он в рабочем коде - доставит куда больше неприятностей) - когда преобразование char* в string возникает автоматически, неявно, из-за правил согласования типов:

Код: Выделить всё

#include <iostream>
#include <string>

using namespace std;

int main()
{
    const char *s1 = "<строка 1>",
               *s2 = "<строка 2>",
               *s3 = "<строка 3>",
               *s4 = NULL;

    string st1 = string(s1) + s2 + s3;
    cout << st1 << endl;

    string st2 = string(s1) + s2 + s4 + s3;
    cout << st2 << endl;
}

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/string_null$ make
g++ -Wall -pedantic -std=c++2a nullconc.cc -o nullconc
здесь s1, s2, s3, s4 - могут вычисляться предварительно в самых замысловатых ситуациях:

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/string_null$ ./nullconc 
<строка 1><строка 2><строка 3>
Ошибка сегментирования (стек памяти сброшен на диск)
Итог тот же - сокрушительный крэш всего приложения!
Вложения
nullconc.cc
(361 байт) 8 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 14878
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: С++ в относительно новых стандартах

Непрочитанное сообщение Olej » 03 июн 2021, 20:14

Olej писал(а):
03 июн 2021, 19:50
Ещё более разительный пример
Ещё интереснее, тот образец, который я нашёл делая порученную ревизию файлов очень крупного (GIT репозиторий порядка 10 тыс. файлов, 3Gb) коммерческого проекта, успешно эксплуатирующегося крупнейшим мировым заказчиком-клиентом уже порядка 7 лет :-o ...
- внутренняя член-переменная класса типа string (сохранённое состояние);
- конструктор класса создаёт объекты из текстовых строк char*;
- вот так:

Код: Выделить всё

#include <iostream>
#include <string>

using namespace std;

class aroud_text
{
public:
    aroud_text(const char* t) : text(t) {}
    friend ostream& operator <<( ostream& out, const aroud_text& obj )
    {
        return out << obj.text;
    }
private:
    string text;
};

int main()
{
    const char *s1 = "<строка 1>";
    aroud_text at1(s1);
    cout << at1 << endl;

    const char *s2 = "<строка 2>";
    aroud_text at2(s2);
    cout << at2 << endl;

    const char *s3 = NULL;
    aroud_text at3(s3);
    cout << at3 << endl;
}

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/string_null$ make
g++ -Wall -pedantic -std=c++2a nullbase.cc -o nullbase

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/string_null$ ./nullbase 
<строка 1>
<строка 2>
terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_M_construct null not valid
Аварийный останов (стек памяти сброшен на диск)
Но и это ещё не всё!
В реальных достаточно объёмных проектах:
- переменная-член text будет описана в одном файле aroud_text.h ...
- а конструктор с инициализацией - в совершенно другом файле aroud_text.cpp ...
- и зачастую эти файлы находятся даже в разных каталогах!
В заголовочном файле aroud_text.h мы видим член-переменную text и наличие конструктора, инициализирующегося строкой char* - всё ОК.
А в реализационном файле aroud_text.h, в конструкторе, мы видим инициализацию переменной text значением параметра - всё, кажется, ОК.
А складывая одно с другим - получаем жестокий крах приложения на выполнении, runtime, и необходимость разгребать дамп и выяснять где в нескольких тысячах строк кода это могло произойти. :-o :evil:
Вложения
nullbase.cc
(543 байт) 8 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 14878
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: С++: фичи, трюки и ловушки

Непрочитанное сообщение Olej » 06 июн 2021, 11:14

Olej писал(а):
03 июн 2021, 20:14
- конструктор класса создаёт объекты из текстовых строк char*;
Подумалось: может POSIX C strdup() ведёт себя по-другому?
Переделал предыдущий код так:

Код: Выделить всё

#include <iostream>
#include <string.h>

using namespace std;

class aroud_text
{
public:
    aroud_text(const char* t) : text(::strdup(t))
    {
	cout << "oject create" << endl;
    }
    ~aroud_text(void) { delete text; }
    friend ostream& operator <<( ostream& out, const aroud_text& obj )
    {
	if (obj.text)
	    return out << obj.text;
	else
	    return out << "NULL";
    }
private:
    const char* text;
};

int main()
{
    const char *s1 = "<строка 1>";
    aroud_text at1(s1);
    cout << at1 << endl;

    const char *s2 = "<строка 2>";
    aroud_text at2(s2);
    cout << at2 << endl;

    const char *s3 = NULL;
    aroud_text at3(s3);
    cout << at3 << endl;
}
В конструкторе добавил диагностику чтобы понимать: в конструкторе произошёл облом, или при последующем выводе в поток...

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/string_null$ make
g++ -Wall -pedantic -std=c++2a nulldup.cc -o nulldup

Код: Выделить всё

olej@R420:~/2021/OWN_TEST.codes/string_null$ ./nulldup 
oject create
<строка 1>
oject create
<строка 2>
Ошибка сегментирования (стек памяти сброшен на диск)
Вот так :twisted:

P.S. Кстати, в man strdup поспешили написать что эта вся группа API (strdup, strndup, strdupa, strndupa) "Thread safety", но ни слова про то, что при вызове strdup(NULL) происходит полный аварийный облом приложения. :-o

Аватара пользователя
Olej
Писатель
Сообщения: 14878
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: С++: фичи, трюки и ловушки

Непрочитанное сообщение Olej » 15 сен 2021, 16:39

Olej писал(а):
06 июн 2021, 11:14
P.S. Кстати, в man strdup поспешили написать что эта вся группа API (strdup, strndup, strdupa, strndupa) "Thread safety", но ни слова про то, что при вызове strdup(NULL) происходит полный аварийный облом приложения.

Код: Выделить всё

olej@R420:~$ man strdup
STRDUP(3)                                                Linux Programmer's Manual                                               STRDUP(3)

NAME
       strdup, strndup, strdupa, strndupa - duplicate a string
...
DESCRIPTION
       The  strdup()  function  returns  a pointer to a new string which is a duplicate of the string s.  Memory for the new string is ob‐
       tained with malloc(3), and can be freed with free(3).
...
Написано: возвращает копию строки.
И strdup(NULL), по логике написанного, должна бы возвращать NULL ... а возвращает "Ошибка сегментирования" :-o

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя