утечка памяти, несмотря на освобождение char **?

кажется, что мое освобождение не работает должным образом. Мне нужно скопировать содержимое std::vector ‹ uint64_t > в char**, потому что используемая C-библиотека хочет этого. Для этого я написал следующий код.

Проблема. Является ли free(storage[key]) неправильным выбором, я забыл какое-то дополнительное освобождение? Потому что после этой свободной команды объем занятой памяти остается прежним, согласно «системному монитору» Ubuntu. Даже после повторного входа в основную функцию занятая память программы по-прежнему составляет около 30 МБ, что должно быть слишком много.

Я уже использовал для этого valgrind, который, кажется, не сообщает об утечке памяти.

Забавная вещь: если я несколько раз вызываю problematic_function(), объем занятой памяти в системном мониторе остается на уровне 30 МБ и не удваивается. Это только ошибка "системного монитора"?

bool problematic_function(std::vector<uint64_t>& hashTableKeys){

   // get number of keys
   double numberOfKeys = hashTableKeys.size();

   // copy keys into char** 'storage', because C-lib wants it like this.
   char** storage = (char**)calloc( numberOfKeys, sizeof(char*));
   if (storage!=nullptr){ // if calloc() succeeded
      for(int key = 0; key < numberOfKeys; key++){
         std::string keyString = std::to_string(hashTableKeys[key]);
         storage[key] = (char *)calloc( (keyString.length()+1), sizeof(char));
         if (storage[key]!=nullptr){ // if calloc() succeeded
            std::strcpy(storage[key], keyString.c_str());
         }
         else{
            return false;
         }
      }
   }
   else{
      return false;
   }

   // pass the char** to the c-lib-function
   // ...

   // de-allocate the memory-blocks
   for(int key=0; key < numberOfKeys; key++){
      free(storage[key]);
   }
   free(storage);
   hashTableKeys.clear();
   return true;
}

int main(int argc, char **argv)
{
   /*
    * checking if memory is freed
    */

   int numberOfModelKeys = 1000000;

   // construct arbitrary keys
   std::vector<uint64_t> hashTableKeys;
   for (int i = 0; i < numberOfModelKeys ; i++){
      uint64_t hashKey = static_cast<uint64_t>(i);
      hashTableKeys.push_back(hashKey);
   }

   problematic_function(hashTableKeys);

   /*
    * at this point the occupied memory of the program (as shown in ubuntu's system-monitor)
    * is still about 30 MiB, although it probably should be a lot less.
    */
 }

person phil_rudd69    schedule 20.02.2019    source источник
comment
Мне любопытно, почему вы хотите использовать char**, когда вы показали, что хотите и можете использовать как std::vector, так и std::string.   -  person François Andrieux    schedule 20.02.2019
comment
Не проблема, но почему numberOfKeys это double?   -  person NathanOliver    schedule 20.02.2019
comment
Обратите внимание, что если (storage[key]!=nullptr когда-либо равно true, вы не сможете free выполнить любые предыдущие распределения, которые вы ранее выполняли в своей функции.   -  person François Andrieux    schedule 20.02.2019
comment
Это единственное место, где вы размещаете вещи? Вектор тоже выделяет вещи - когда это выходит за рамки?   -  person doctorlove    schedule 20.02.2019
comment
На некоторых платформах freeing память не обязательно означает, что она сразу возвращается в ОС. Приложение может по-прежнему владеть правами на эти страницы памяти, чтобы ускорить выделение памяти в будущем. Таким образом, освобожденная память может по-прежнему отображаться как используемая с точки зрения ОС.   -  person François Andrieux    schedule 20.02.2019
comment
Что ваша c-lib-функция делает с char **?   -  person doctorlove    schedule 20.02.2019
comment
@FrançoisAndrieux спасибо! Не думал о том, что предыдущие распределения.   -  person phil_rudd69    schedule 20.02.2019
comment
@ phil_rudd69 Если вы придерживаетесь объектов-контейнеров, вам не придется беспокоиться о запоминании таких вещей. Одна из нескольких причин, по которым больше не используется ручное выделение памяти.   -  person François Andrieux    schedule 20.02.2019
comment
@doctorlove передается в c-lib для создания хеш-функции из hashKeys. см. cmph.sourceforge.net   -  person phil_rudd69    schedule 20.02.2019
comment
@NathanOliver, потому что в моем реальном сценарии число может стать действительно большим.   -  person phil_rudd69    schedule 20.02.2019
comment
@phil_rudd69 Тогда используйте std::size_t. Использование двойного числа является неправильным подходом, и если число действительно велико, вы получите неправильный результат, поскольку оно может точно представлять только целые числа размером 53 бита. std::size_t, с другой стороны, гарантированно работает.   -  person NathanOliver    schedule 20.02.2019
comment
@NathanOliver спасибо, если я изменю его на std::size_t, нужно ли мне что-то еще менять?   -  person phil_rudd69    schedule 20.02.2019
comment
Вы также должны изменить key (индексы вашего цикла) на size_t, так как int может быть меньше, чем size_t.   -  person NathanOliver    schedule 20.02.2019
comment
@FrançoisAndrieux Я не понимаю, как мне избежать этих ручных выделений, поскольку вышеупомянутая c-lib принимает только char ** в качестве входных данных. :)   -  person phil_rudd69    schedule 20.02.2019
comment
@ phil_rudd69 Если количество элементов не помещается в int, то ваши for циклы все равно не будут работать.   -  person François Andrieux    schedule 20.02.2019
comment
@NathanOliver, очевидно: D, спасибо   -  person phil_rudd69    schedule 20.02.2019
comment
@phil_rudd69 data() std::vector<char*> это char**. Затем вы можете построить этот вектор, назначив data() каждого элемента в std::vector<std::string>.   -  person François Andrieux    schedule 20.02.2019
comment
@FrançoisAndrieux тоже имеет смысл ... Мне нужно проверить свой исходный код. Было бы глупой ошибкой.   -  person phil_rudd69    schedule 20.02.2019
comment
@FrançoisAndrieux, это приятно знать. Не знал, что данные std::vector‹char*› были char**. Избавил бы меня от головной боли.   -  person phil_rudd69    schedule 20.02.2019
comment
@ phil_rudd69 Одним из важных свойств std::vector является то, что все элементы гарантированно хранятся в непрерывном массиве, а член data() std::vector<T> всегда возвращает T* (за исключением std::vector<bool> по глупым причинам). Вместо этого его можно использовать всякий раз, когда вы можете использовать динамически выделяемый массив.   -  person François Andrieux    schedule 20.02.2019