-3

I want to initialize a program with some configuration data. It receives them as an url-encoded json via argv[], decodes and deserializes them and hand them to a method inside a class, that is supposed to set the relevant variables to the submitted values.

The variables have the type c-string, so i do the conversion first before assigning the values to the variables declared outside the method. If reading out those newly set values from the pointers, everything is fine, as long as i am staying inside this method. Upon leaving it, the variables with their values set from the handed configuration data do contain garbage only, while the one filled from the string literal is perfectly ok.

I printed out information about the variables type (typeid().name()). While they are not necessarily human readable, comparison between shows, that they all are of the type they are supposed to be. Next is compared the values of the pointers inside and out side the method - they were the same.

/* Config.cpp */
using json = nlohmann::json;

const char *Config::DB_HOST;
const char *Config::DB_USER;
const char *Config::DB_PASSWORD;
const char *Config::DB_NAME;
const char *Config::DB_SOCK;

Config::Config() {}

void Config::initDB(json dbConfig) {
    string host = dbConfig["host"];
    DB_HOST = host.c_str();
    string user = dbConfig["user"];
    DB_USER = user.c_str();
    string pass = dbConfig["pass"];
    DB_PASSWORD = pass.c_str();
    string name = dbConfig["name"];
    DB_NAME = name.c_str();

    DB_SOCK = "/var/run/mysqld/mysqld.sock";
}

I am especially puzzled about the differences between the values set by the variables and the value set by the string literal. The first fails, the latter works. Whats the difference between them? After some reading in the forum i had the understanding, c_str() should return exactly the same data type (a null-terminated pointer to the data) as the literal.

Thanks for your hints!

Nexus
  • 1
  • 1
  • I dont consider this a duplicate. The answer is, but in order to recognize this, you have to know it. If i knew the answer, i would not have asked. – Nexus Aug 13 '19 at 06:50

2 Answers2

3

When you do string host = dbConfig["host"]; you create a function local std::string that has a copy of what dbConfig["host"] returns. You then geta pointer to that local string data using c_str(). At the end of the function that local string is destroyed so now you have a pointer to garbage.

DB_SOCK = "/var/run/mysqld/mysqld.sock"; on the other hand is different. "/var/run/mysqld/mysqld.sock" is a string literal and it has static storage duration meaning it will live until the end of the program. This is why that field is still valid after the function ends.

The rule is you cannot take a reference or pointer to a function local object unless that object is static. If not it is destroyed leaving you with a dangling pointer/reference.


If dbConfig["host"] returns a stable reference to a std::string then you can use DB_HOST = dbConfig["host"].c_str();. If not then I would suggest changing the variables to std::string's so they copy correctly.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • Unfortunately its impossible to change to std::string, because other parts of the program (not by me) expect them to be c-strings. So i guess i have to define them static before filling them up and creating the pointer? – Nexus Aug 12 '19 at 15:40
  • It really really depends. First off, are you sure you can't keep them as strings? You can just change `some_name` to `some_name.c_str()` every place you actually need a c-string. If not then you could make those local strings `static`, but that means you can ever only call that function once. If you need to be able to call it multiple times then either the strings need to be made `static`, and then assigned to later, or come up with a different solution. – NathanOliver Aug 12 '19 at 15:44
  • As this is a configuration function, calling it once only is not only no disadvantage, but even desirable, so i will go with a local static, and just take care about eventual errors. @all Thanks again for your help – Nexus Aug 12 '19 at 17:15
2

You store local variable inside member with:

string host = dbConfig["host"];
DB_HOST = host.c_str();

so you have dangling pointer once host goes out of scope.

literal c-strings have static duration, so you may store it in global without lifetime issue.

You might change member from const char* to std::string to avoid that issue:

std::string Config::DB_HOST;
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • It's important to understand that `c_str()` gives you a pointer to the data that the `string` you called it on is storing. If the `string` is changed or ceases to exist, then the pointer points to whatever junk happens to wind up in that same location of memory. – David Schwartz Aug 12 '19 at 15:27
  • Argh... obvious once pointed to, thanks. The availability of the pointer fooled me (not yet accustomed to the whole pointer-concept...) – Nexus Aug 12 '19 at 15:31
  • @DavidSchwartz It's not just a matter of the pointer pointing to garbage. It is far worse; it's Undefined Behaviour and thus renders the *entire* program invalid and the compiler is free to emit *whatever it wants* for *all* parts of the code. So you can trust *nothing*. – Jesper Juhl Aug 12 '19 at 15:32