0

I'm building quick "Configuration Reader" which reads settings from files. Problem is, that function returns "800" character string and atol with that input returns 0. I'm not sure what I'm doing wrong.

So, this is how it roughly looks like:

char *txtval=GetParamFromLine("WINDOW_WIDTH");
val = atol(txtval); 

char* GetParamFromLine(char*parameter)
{
   char text[16];
   //
   // do the reading procedure and fill the text
   //
   return text;
}

Result: atol(text) = 0 (where text = "800" )

Thanks for your help!

Casey
  • 10,297
  • 11
  • 59
  • 88
Blodyavenger
  • 170
  • 1
  • 12
  • Are you definitely NULL terminating the string in `GetParamFromLine()`? – Philip Kendall Sep 05 '13 at 20:51
  • 14
    You're returning the pointer to a local variable (text) from GetParamFromLine. – Eugen Constantin Dinca Sep 05 '13 at 20:51
  • As Eugen pointed out you are returning a pointer to a local variable that no longer exists at the point of use. You should not provide information that is wrong and misleading. `text` is **not** `"800"`. Use a debugger in the future. – IInspectable Sep 05 '13 at 20:54
  • Yes that would be it. I feel lame right now...but I got confused because after debugging it looked like function returned the right thing. – Blodyavenger Sep 05 '13 at 20:54
  • did u check with a debugger that txtval really contains "800" (Eugen points out why it wont). If you pass "800" to atol and it returns 0 then your implementation of atol is broken; this seems unlikely. Thus the likely explanation is that you are not passing '800' to it. – pm100 Sep 05 '13 at 20:55
  • 1
    C++ tag, is it valid? If it is, return std::string by value, problem solved. Do not fool around with C style strings in C++ programs (without a very good reason). Or remove the c++ tag if you are restricted to C. – hyde Sep 05 '13 at 21:01
  • 2
    ...and, of course, forget about `atol`. Use `strtol`, if you need to use a C standard library function. – AnT stands with Russia Sep 05 '13 at 21:11
  • I could sware that there was a value "800" in txtval when I was debugging. I wanted to make a screenshot but now it's ""... – Blodyavenger Sep 05 '13 at 21:29

4 Answers4

4

You need to have "text" stored on the heap instead of the stack. Otherwise the variable goes of scope when the function ends, and you'll have undefined behavior trying to access that segment of memory. You can either allocate memory on the heap and then free it when you're done,

char *txtval=GetParamFromLine("WINDOW_WIDTH");
val = atol(txtval); 
free txtval;

// Caller is responsible for freeing returned buffer
char* GetParamFromLine(char*parameter)
{
   char text* = malloc(sizeof(char)*16);
   //
   // do the reading procedure and fill the text
   //
   return text;
}

Or alternatively pass in a pointer to a buffer where the result should be stored.

char txtval[16];
GetParamFromLine("WINDOW_WIDTH", txtval);
val = atol(txtval); 

void GetParamFromLine(char *parameter, char *resultBuffer)
{
   //
   // do the reading procedure and fill the text into resultBuffer
   //
}
PherricOxide
  • 15,493
  • 3
  • 28
  • 41
3

You're returning a pointer to a locally scoped variable. The memory text goes out of scope after the function call, so a pointer to it has no meaning as the memory will be reclaimed...

You'll need to allocate some longer-term storage for text, or combine the GetParamFromLine and atol into the same function.

Ross
  • 1,313
  • 4
  • 16
  • 24
  • I'm not sure that "permanent storage" is the right phrase. You could just return strdup(text). – dcaswell Sep 05 '13 at 21:01
  • +1 for suggestion to combine `atol` into `GetParamFromLine`, but I would suggest that a new function be created for the purpose: `GetLongParamFromLine`. – jxh Sep 05 '13 at 21:02
  • Yes, that was the intention but then I would have many functions: GetLongFromLine GetStringFromLine GetFloatFromLine so I wanted to fetch parameter value and do with it whatever I want. – Blodyavenger Sep 05 '13 at 21:04
2

Make text static:

char* GetParamFromLine(char*parameter)
{
    static char text[16];
    ... do something
    return text;
}

Your version is using a stack-resident version that will probably be overwritten before it is used. The static storage class specifier ensures that the

That returned buffer will only be good up until the next call to GetParameterFromLine.

It's better, in general, for the caller to provide the buffer and a buffer size as arguments to a function like this.

char *GetParamFromLine(const char *parameter, char *text, size_t text_size)
{
    ... do something
    return text;
}

The "do something" code should carefully observe that size. You will have to decide whether to truncate the parameter, return an empty string, return a NULL pointer, or whatever if the actual data is longer than the buffer.

Mike Housky
  • 3,959
  • 1
  • 17
  • 31
  • Alternately, pass the target buffer as a parameter. – John Bode Sep 05 '13 at 20:54
  • 2
    And hope that nobody calls any of this code in a multi-threaded context. – Philip Kendall Sep 05 '13 at 20:54
  • or do return strdup(text); or use std::string – pm100 Sep 05 '13 at 20:56
  • But then again this would result in many static char array if function is called many times. – Blodyavenger Sep 05 '13 at 21:03
  • @All: OP wanted char[] arrays, so he gets char[] arrays. The static local buffer isn't evil...just limited. The caller-provided buffer is better, but less easy to use. – Mike Housky Sep 05 '13 at 21:07
  • @PhilipKendall: I suppose I could have make the buffer thread_local to handle the thread case, but that seems like swatting flies with a sledge hammer. I traced out MS's implmentation once to find out why their rand() was so slow. Turns out the seed it uses is thread_local. It (VC++) takes over 100 instructions to find out where thread local is. – Mike Housky Sep 05 '13 at 21:11
  • Making it static is not thread-safe - don't do that! – mvp Sep 05 '13 at 21:14
  • 1
    @mvp *"Don't do that!"* is dogmatic. Don't do that! – IInspectable Sep 05 '13 at 21:18
  • @IInspectable: I would "don't do that" your statement once more, but I don't like recursion either :) – mvp Sep 05 '13 at 21:41
2

The problem is caused by returning a pointer to a local variable. When the function GetParamFromLine returns the variable text goes out of scope. The pointer returned is no longer valid, resulting in undefined behavior. You have to return the data in some other way, for example:

std::string txtval=GetParamFromLine("WINDOW_WIDTH");
val = atol(txtval.c_str());

std::string GetParamFromLine(char*parameter)
{
   char text[16];
   //
   // do the reading procedure and fill the text
   //
   return std::string(text);
}

Changing the return type from a pointer to a local to std::string alleviates the problem of returning a pointer to a local variable. The resources used by std::string are managed by the object and are cleaned up automatically.

As an alternative you could remove the atol call as well and use a std::istringstream instead. One benefit is that this allows for better error reporting:

std::string txtval=GetParamFromLine("WINDOW_WIDTH");
std::istringstream iss(txtval);
int val( 0 );
iss >> val;

Now if you really want to go over the top you could combine both retrieval of the string and conversion to an integral data type, and parameterize the return type of this new function template:

template<typename T>
T GetValueFromLine(const std::string& param) {
    std::string txtval=GetParamFromLine(param.c_str);
    std::istringstream iss(txtval);
    T val = T();
    iss >> val;
    return val;
}

With this template you can do the following:

long wndWidth = GetValueFromLine<long>("WINDOW_WIDTH");
bool showInfo = GetValueFromLine<bool>("SHOW_INFO");
....

Note: Error handling omitted.

IInspectable
  • 46,945
  • 8
  • 85
  • 181