Форум программистов, компьютерный форум, киберфорум
C/С++ под Linux
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.69/13: Рейтинг темы: голосов - 13, средняя оценка - 4.69
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14

Не освобождается память std::string после использования std::bind

06.12.2019, 13:28. Показов 2894. Ответов 27
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Всем привет!

Есть система, которая подгружает из внешних библиотек функции, упаковывает их в std::bind и заносит в std::map<std::string, std::function>. В дальнейшем, как это очевидно, по некой произвольной строке мы в map находим нужную нам функцию и ее выполняем. Система написана на С++11 под Linux (Ubuntu 18.04)

В самом коде, который реализует этот механизм утечек памяти нет (я надеюсь, проверял разными способами). А вот дальше начинается магия. Если в импортируемой функции буду использовать std::string, то память после выполнения импортированной функции не будет освобождаться.

Бодаюсь с этим уже третий день ))

Три вопроса:
1. Что за фигня происходит? Может кто-то сталкивался с подобным поведением.
2. Какие инструменты можно использовать для поиска утечек памяти? Может я еще не все испробовал )
3. Кто готов покопаться в коде системы и поискать ошибку (за вознаграждение, естественно)?
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
06.12.2019, 13:28
Ответы с готовыми решениями:

Std::string портит память
Столкнулся с интересной проблемой. Лет 6 назад использовал одну прогу под ASPLinux 7.2. Исходники компилировались нормально и прога...

Освобождение памяти после std:bind
Что имею: - CentOS - gcc 4.8 - valgrind Что делаю - С помощью valgrind устраняю утечки - Если приложение остановлено...

Не могу разобраться как обновить в std::map<std::string, вектор_структур>
Не могу разобраться как обновить вектор структур после его добавления в map без удаления и перезаписи struct pStruct { int...

27
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
06.12.2019, 15:49
Цитата Сообщение от igor_goryachev Посмотреть сообщение
В самом коде, который реализует этот механизм утечек памяти нет (я надеюсь, проверял разными способами). А вот дальше начинается магия. Если в импортируемой функции буду использовать std::string, то память после выполнения импортированной функции не будет освобождаться.
Что значит "в импортируемой функции буду использовать std::string"?
Покажи эту функцию
0
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
07.12.2019, 22:07  [ТС]
Печаль всей ситуации в том, что не важно, что внутри... Изначально был код, который открывал один файл, что-то с ним делал и записывал в другой файл. Вот такой код в системе выполняется бесконечно (сам на себя зациклен):

C
1
2
3
4
5
6
7
8
9
10
std::string A2Files::test(const std::shared_ptr<message_t2> &message, const Json::Value &route_params)
{ LOG_TRACE
  /////////////////////////////////////////////////////////////////////////////////////
 
      std::cout << "[END TEST]" << std::endl;
      this->sendMessage(this->name,"/test",message->root);
 
  /////////////////////////////////////////////////////////////////////////////////////
  return "success2";
};
При этом утечки памяти нет. Все стабильно и радостно )

Сейчас я свел все к такой примитивной штуке:

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
std::string A2Files::test(const std::shared_ptr<message_t2> &message, const Json::Value &route_params)
{ LOG_TRACE
  /////////////////////////////////////////////////////////////////////////////////////
 
      std::string *data = new std::string(" ");
      
      for(int i = 0; i < 500000; i++)
      {
         data->append("<sdfdsfsdfdsff><bbnknhjkjlkjk>sdfdsfds</sdfdsfdsf></sdfdsfd>\r\n");
      } 
 
      data->clear();
      delete data;
 
      this->sendMessage(this->name,"/test",message->root);
 
  /////////////////////////////////////////////////////////////////////////////////////
  return "success2";
};
И каждый вызов функции приводит к увеличению памяти *(
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
08.12.2019, 11:10
Цитата Сообщение от igor_goryachev Посмотреть сообщение
При этом утечки памяти нет. Все стабильно и радостно )
Цитата Сообщение от igor_goryachev Посмотреть сообщение
И каждый вызов функции приводит к увеличению памяти *(
Здесь утечки нет. Она где-то в другом месте, либо в sendMessage, либо в твоём json-парсере.
Я так понимаю, ты используешь gcc. Не знаю, как смотреть там утечки (обычно достаточно посмотреть код), но может вот это поможет https://github.com/google/sani... kSanitizer

Ну и, если уж создаёшь динамические объекты, пользуйся std::unique_ptr
C++
1
2
3
4
5
6
7
8
 /////////////////////////////////////////////////////////////////////////////////////
 
       std::unique_ptr<std::string> data{new std::string(" ")};
      
      for(int i = 0; i < 500000; i++)
....................
      //data->clear();
      //delete data;
0
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
08.12.2019, 12:04  [ТС]
Спасибо за рекомендации!

Как вы правильно написали, в приведенном коде ошибок нет. Но достаточно убрать из функции код работы со строкой, как утечка памяти прекращается. Соответственно, утечек памяти ни в sendMessage, ни в Json парсере нет. Может ли это быть связано с тем, что функция вызывается не напрямую, а через std::bind?
0
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,818
08.12.2019, 13:14
igor_goryachev, в вашей ситуации их общих рекомендаций можно только отметить, что лучше не передавать объекты стандартной библиотеки через границы модулей. Это чревато нарушением ODR и последующим за этим неопределенным поведением программы.
0
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
10.12.2019, 13:56  [ТС]
Я передаю за пределы модулей объекты. Может в этом проблема )

1. Есть основная программа. В ней реализован некоторый базовый класс и "реестр" подключаемых библиотек.

2. Внутри каждой библиотеки реализован один класс (наследуемый от базового). Сам класс передается в основную программу примерно так:

C++
1
2
3
4
extern "C" void addFactory(ActorList *factory)
{
   factory->add<A2Files>("A2Files");
}
Класс ActorList реализован тоже довольно просто:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class ActorList
{
  private:
    std::map <std::string, Actor2*> actors;  // коллекция акторов по именам
    std::map <std::string, std::function<Actor2*(std::string, Json::Value)> > FM;
    std::map<std::string, std::function<std::string(const std::shared_ptr<message_t2>, const Json::Value &)> > *ActorRouter;
 
    std::mutex create_actor_mutex;
 
  protected:    
 
   // Функция создания актора
       template<class C> C* createA(const std::string & name, Json::Value data) const { return new C(name,data); };
 
  public: 
 
 
    ActorList();
    ~ActorList();
 
   void init();
    
 
   template <class C>
   void add(const std::string & type)
   {
       auto it = this->FM.find(type);
       if (it == this->FM.end())
       {
          this->FM.insert( std::pair<std::string, std::function<Actor2*(std::string, Json::Value)>>(type, std::bind(&ActorList::createA<C>, this, std::placeholders::_1, std::placeholders::_2) ) );
       }
    };
 
}
Я не вижу где тут может возникнуть нарушение ODR...
0
фрилансер
 Аватар для Алексей1153
6465 / 5679 / 1131
Регистрация: 11.10.2019
Сообщений: 15,121
10.12.2019, 14:04
igor_goryachev, с объектом класса должен работать тот модуль, который его создал. Тот же самый модуль должен и удалять объект. Наружу можно передавать указатель на созданный объект. На любой чих нужно передавать указатель в создавший объект модуль, чтобы тот модуль вызвал нужную функцию объекта

Это всё потому, что std::string в одном модуле может быть устроен совсем иначе, чем std::string в другом, если это разные процессы
0
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,818
10.12.2019, 14:15
Цитата Сообщение от Алексей1153 Посмотреть сообщение
std::string в одном модуле может быть устроен совсем иначе, чем std::string в другом,
igor_goryachev, Даже если они устроены одинаково. ODR мы все равно нарушим, ведь почти вся внутрянка std::string - это функции в заголовочном файле. В одном модуле (библиотеке) будут в итоге одни определения, а в другом (программе, которая ее использует) - другие. При этом к объекту будут сначала применяться одни версии этих функций (пока он на стороне библиотеки), а затем другие (когда его получили на стороне программы). Это и есть нарушение ODR.
Нарушение ODR как правило не диагностируется, и далеко не всегда приводит к видимым проблемам. Этим оно и опасно.
0
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
10.12.2019, 14:15  [ТС]
Процесс один. Потоки разные...

Спасибо за указание куда думать. Фактически, сейчас у меня библиотека отдает основной программе "право" создавать и управлять классом, который реализован в этой библиотеке. Нужно же переписать, что управление жизненным циклом должно быть реализовано в библиотеках, а основная программа должна просто вызывать соответствующие импортированные из библиотек функции?

Грубо говоря, вместо

C++
1
extern "C" void addFactory(ActorList *factory)
У меня должно быть что-то типа такой функции:

C++
1
2
3
4
extern "C" A2Files * new_actor()
{
    return new A2Files();
}
0
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,818
10.12.2019, 14:22
igor_goryachev, есть отличная книга, Martin Reddy - API Design for C++.
Там есть ответы на почти все вопросы о том, как правильно дизайнить библиотеки и плагины с использованием С и С++.
2
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
10.12.2019, 14:25  [ТС]
Спасибо, почитаю
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
11.12.2019, 10:52
Цитата Сообщение от igor_goryachev Посмотреть сообщение
Если в импортируемой функции
тебя в этой фразе ничего не настораживает?

Цитата Сообщение от igor_goryachev Посмотреть сообщение
Я передаю за пределы модулей объекты. Может в этом проблема
есть такой железобетонный принцип:
кто память выделял, тот и должен её освобождать.

схема выглядит примерно так:

C++
1
2
3
4
5
// ресурс создаётся менеджером импортированной функции
auto* resource = api::create(); 
 
// возвращаем ресурс менеджеру на стороне модуля
api::free(resource);
на принимающей стороне у тебя один менеджер памяти,
на стороне модуля - другой.

если выделять память одним, а освобождать другим,
то утечки памяти - это ты ещё легко отделался.
0
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
11.12.2019, 16:57  [ТС]
hoggy, Все манипуляции со строкой происходят в пределах одной функции. За пределы модуля передается объект. Функция объекта оборачивается в std::bind и много раз вызывается. Т.е. строка там где создается там и удаляется. передачи строки за границы модуля (даже функции) не осуществляется.

Добавлено через 2 минуты
Я сейчас читаю рекомендованную книжку и с нуля пишу весь код, собирая его по кусочкам ) Теперь постараюсь ошибок подобных не совершить
0
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
12.12.2019, 17:27  [ТС]
DrOffset, oleg-m1973, hoggy

Всем привет!

Переписал я свой код с учетом всех рекомендаций. Запустил. Модули динамически загружаются, функции вызываются, память освобождается. Полная красота и гармония! В однопоточном режиме.

Запустил это все дело на пуле потоков и получил утечку памяти ( Видимо, дело в пуле потоков
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
12.12.2019, 17:36
Цитата Сообщение от igor_goryachev Посмотреть сообщение
Запустил это все дело на пуле потоков и получил утечку памяти ( Видимо, дело в пуле потоков
Это вряд ли. Скорее, проблема в твоём коде.
0
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
12.12.2019, 17:41  [ТС]
oleg-m1973, Почему? В однопоточном режиме все отлично же
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
12.12.2019, 17:43
Цитата Сообщение от igor_goryachev Посмотреть сообщение
oleg-m1973, Почему? В однопоточном режиме все отлично же
Не знаю. Я ни того ни другого не видел.
0
0 / 0 / 0
Регистрация: 02.02.2016
Сообщений: 14
12.12.2019, 20:37  [ТС]
Уважаемые знатоки!

Вот какой вопрос )

Есть примитивный до безумия класс:


C++
1
2
3
4
5
6
7
8
9
10
11
class ThreadPool
{
  public:
 
    ThreadPool();
    ~ThreadPool();
 
    void thread_fn(int i);
    void test();
 
};
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include "thread.h"
 
 
ThreadPool::ThreadPool(){  };
ThreadPool::~ThreadPool(){ };
 
    void ThreadPool::thread_fn(int i)
    {
       while (true)
       {
          this->test();
          std::cout << "thread" << std::endl;
          std::this_thread::sleep_for(std::chrono::seconds(3));
       }
    };
 
    void ThreadPool::test()
    {
       std::string tmp(" ");
 
       for(int i = 0; i < 500000; i++)
       {
          tmp.append("<test>data</test>");
       }
       std::this_thread::sleep_for(std::chrono::seconds(3));
    };
Пишу вот так в однопоточном режиме:

C++
1
2
3
4
5
int main(int argc, char* argv[])
{   
   auto threads = new ThreadPool();
   threads->thread_fn(10);
}
Запускаю команду top и наблюдаю как прыгают цифры потребления памяти. Сначала память растет (в момент выполнения функции ThreadPool::test(), а потом освобождается. Все великолепно.

Немного меняю код
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char* argv[])
{   
 
   auto threads = new ThreadPool();
 
   std::thread t1(&ThreadPool::thread_fn, threads, 10);
 
   while(true)
   {
      std::this_thread::sleep_for(std::chrono::seconds(3));
   }
 
}
Запускаю top... и не вижу характерных скачков памяти. Память выделилась и не освобождается. (Хорошо хоть не растет дальше).

Внимание вопрос. Почему???
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
12.12.2019, 21:09
Цитата Сообщение от igor_goryachev Посмотреть сообщение
Запускаю top... и не вижу характерных скачков памяти. Память выделилась и не освобождается. (Хорошо хоть не растет дальше).
Внимание вопрос. Почему???
Наверное, потому что top криво работает с потоками (linux всё-таки). Код абсолютно одинаковый (кривой, конечно, но это другое)
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
12.12.2019, 21:09
Помогаю со студенческими работами здесь

std::string, std::fstream, ошибка кучи
где то начало вылетать при операции += с локальной переменной std::string. Заменил на свой qString. Замечательно, то же самое... ошибка при...

ошибка error: cannot convert 'std::string {aka std::basic_string<char>}' to 'std::string* {aka std::basic_stri
на вод поступают 2 строки типа string. определить количество вхождений строки 2 в строку 1 ошибка error: cannot convert 'std::string {aka...

Std::bind, std::mem_fun, std::mem_fn
В чем разница между функциями std::bind, std::mem_fun, std::mem_fn?

Как можно еще использовать std::placeholders вне в связки с std::bind?
Добрый день! Как можно еще использовать std::placeholders вне в связки с std::bind?

В чем отличия между std::cref() и std::bind()?
В документации не понял, что делает bind() ? И чем отличается cref() от операции взятия адреса? int x; int *y = &amp;x;...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Новые блоги и статьи
Отправка уведомления на почту при изменении наименования справочника
Maks 24.03.2026
Программная отправка письма электронной почты на примере изменения наименования типового справочника "Склады" в конфигурации БП3. Перед реализацией необходимо выполнить настройку системной учетной. . .
модель ЗдравоСохранения 5. Меньше увольнений- больше дохода!
anaschu 24.03.2026
Теперь система здравосохранения уменьшает количество увольнений. 9TO2GP2bpX4 a42b81fb172ffc12ca589c7898261ccb/ https:/ / rutube. ru/ video/ a42b81fb172ffc12ca589c7898261ccb/ Слева синяя линия -. . .
Midnight Chicago Blues
kumehtar 24.03.2026
Такой Midnight Chicago Blues, знаешь?. . Когда вечерние улицы становятся ночными, а ты не можешь уснуть. Ты идёшь в любимый старый бар, и бармен наливает тебе виски. Ты смотришь на пролетающие. . .
Контроль уникальности заводского номера - вариант №2
Maks 24.03.2026
В отличие от предыдущего варианта добавлено прерывание циклов, также добавлены новые переменные для сохранения контекста ошибки перед прерыванием цикла: Процедура ПередЗаписью(Отказ, РежимЗаписи,. . .
SDL3 для Desktop (MinGW): Вывод текста со шрифтом TTF с помощью библиотеки SDL3_ttf на Си и C++
8Observer8 24.03.2026
Содержание блога Финальные проекты на Си и на C++: finish-text-sdl3-c. zip finish-text-sdl3-cpp. zip
Жизнь в неопределённости
kumehtar 23.03.2026
Жизнь — это постоянное существование в неопределённости. Например, даже если у тебя есть список дел, невозможно дойти до точки, где всё окончательно завершено и больше ничего не осталось. В принципе,. . .
Модель здравоСохранения: работники работают быстрее после её введения.
anaschu 23.03.2026
geJalZw1fLo Корпорация до введения программа здравоохранения имела много невыполненных работниками заданий, после введения программы количество заданий выросло. Но на выплатах по больничным это. . .
Контроль уникальности заводского номера - вариант №1
Maks 23.03.2026
Алгоритм контроля уникальности заводского (или серийного) номера на примере нетипового документа выдачи шин для спецтехники с табличной частью, разработанного в конфигурации КА2. Данные берутся из. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru