Search code examples
c++arrayspointersboostini

External Objects Across CPP files (Boost Filesystem/Variants, Libconfini)


I've got a deceptively complex one for y'all.

I am attempting to make a program which makes use of an INI config file (I am using the libconfini C library), which looks like this:

[General]
output_directory = /scratch/miles/trans_assembly_pipeline/

[SRA accessions]
SRR18328591
SRR28481881

It parses the information therein into a map, index-able by section, and then by key, which looks like this (the only important bit really is the char* option in the variant object):

map<string, map<string, boost::variants<int, bool, double, string, char*>>

Since keys in INI files can have various types, I am utilizing Boost's 'Variants' library to allow for multiple value types. Finally, for convenient file management, I am using Boost's 'Filesystem' library.

I have an INI parsing implementation file, which basically just stores the INI data into that map type, defined thus (the header for which, merely containing function declarations and definition directives, can be found at the bottom):

#include "ini_parse.h"


namespace fs = boost::filesystem;

// Dispatch INI data to a map, indexable by sections, then by keys
static int ini_callback(IniDispatch * const dispatch, void * map_pt) {
  #define thismap (reinterpret_cast<INI_MAP*>(map_pt))
  if (dispatch->type == INI_COMMENT) {
    return 0;
  }
  if (dispatch->type == INI_SECTION) {
    INI_MAP_ENTRY newSec;
    thismap->insert(std::pair<std::string, INI_MAP_ENTRY>(dispatch->data, newSec));
    return 0;
  }
  if (dispatch->type == INI_KEY) {
    (*thismap)[dispatch->append_to].insert(std::pair<std::string, BOOST_VAR>(dispatch->data, dispatch->value));
  }
  return 0;
}


// Given a FILE object, return a buffer containing its contents
char * make_ini_buffer(FILE * iniFile) {
  char * iniBuffer;
  long sz; 
  fseek(iniFile, 0L, SEEK_END);
  sz = ftell(iniFile);
  rewind(iniFile);
  iniBuffer = (char*)malloc(sz + 1); 
  fread(iniBuffer, 1, sz, iniFile);
  return iniBuffer;
}


// Given the path/name of an INI config file, return a map of its data
// which can be indexed by sections, and then by keys
INI_MAP make_ini_map(const char * configPath) {
  FILE * configIni = fopen(configPath, "r");
  fs::path configPathObj(configPath);
  try {
    if (!configIni) {
      std::string fileError = "ERROR: Cannot open config file: ";
      throw std::runtime_error(fileError);
    }   
  } catch (std::runtime_error& e){ 
    std::cerr << e.what() << configPathObj.filename() << std::endl;
    return {}; 
  }
  INI_MAP iniMap;
  char * iniBuffer = make_ini_buffer(configIni);
  strip_ini_cache(iniBuffer, strlen(iniBuffer), INI_DEFAULT_FORMAT,
                  NULL, ini_callback, &iniMap);
  delete [] iniBuffer;
  return iniMap;
}


INI_MAP cfgIni = make_ini_map("../config.ini");

Note the final line, outside of any other function, defining 'cfgIni'. The idea is to define a global INI data storage object that can be used externally by other files -- for example, like this:

#include "ini_parse.h"

int main() {
  extern INI_MAP cfgIni;
  extern int size_path;
  
  std::cout << cfgIni["General"]["output_directory"] << std::endl;

  return 0;
}

Now for my problem: When declaring 'cfgIni' externally in this separate file, it appears that bits of memory are getting lost from where it is defined in my implementation file to where it is declared externally and caught here. I have no issue accessing the keys in the parent/child map, but the values are more illusive.

When printing the contents of the section "General", "output_directory" is returned, no problem.

I would expect the final line in the above code snippet to print the filepath char* array, to which "output_directory" is set in the INI file.

However, I instead get some random characters, like "�U". What is even more confusing is when I print out the size of this return value in memory using sizeof(), it returns '40', the correct number of characters in that filepath.

I have linked the documentation for the libraries I used and their functions. Apologies for the length/complexity of this question.

strip_ini_cache(): https://madmurphy.github.io/libconfini/html/confini_8c.html#a25d648d387f6e8bc36e7965accfca63b

Boost.variant: https://www.boost.org/doc/libs/1_61_0/doc/html/variant.html

Boost.filesystem: https://www.boost.org/doc/libs/1_80_0/libs/filesystem/doc/reference.html

Below is my header file:

#include <iostream>
#include <string>
#include <map>
#include <cstdio>

#include <confini.h>
#include <boost/filesystem.hpp>
#include <boost/variant.hpp>

#define BOOST_VAR boost::variant<int, bool, double, std::string, char*>
#define INI_MAP_ENTRY std::map<std::string, BOOST_VAR>
#define INI_MAP std::map<std::string, INI_MAP_ENTRY>



static int ini_callback(IniDispatch * const dispatch, void * map_pt);

char * make_ini_buffer(FILE * iniFile);

INI_MAP make_ini_map(const char * configPath);

Solution

  • The answer to this is really quite trivial. When passed by value, as I had done in one of my files, a shallow copy of the object was created, wherein the pointers to the char arrays were overwritten with wild addresses.

    The solution was to pass by const reference.