Why Do I Lose Data In a Char Pointer When Passed Into or Over a Function?

Started by
22 comments, last by ajm113 9 years, 7 months ago

"Oh hey, look, now when I refactor out that doLogic call to another function it doesn't magically start seeing 4 or 8 or whatever the pointer size is randomly. All because I didn't use sizeof in an easly breakable place!"


Wow, a lot of assumptions! I suggest you read the OP. The takeaways are really simple: "library" + "C". Not c++.

First off, the language marker is C++, he's already mentioned that he's doing this IN C++ and desires C compatibility. That means the public facing interface, not the internal implementation details, need to be something C can handle.

Using sizeof in a manner that is almost universally recognized as being seriosly error prone is A Code Smell(tm) and should be avoided. One common example of this is exactly what was demonstrated above.

I assume the "library" + "C" thing was in reference to refactoring??? In which case: Refactoring is something you should do in any language, it's a means of cleaning up, enhancing, and generally making code better. Which can include moving where a function call happens.

As an additional note I've also removed your other post, keep it constructive and technical. I should also note that this topic is in General Programming. Not For Beginners, as such we generally allow the discussion to range a bit further from the original topic. Note that if this was for beginners, I would be extra hard on the sizeof issue.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

Advertisement

I would string suggest turning the warning levels on your compiler up all the way. Most compilers written in the last few decades would have tagged this error with an appropriate warning. Such warnings are often a good way to help you learn the bad habits to avoid.

In fact, if possible, never turn the warnings off.

Stephen M. Webb
Professional Free Software Developer


return (char*)subbuff;

Crank up the warning level of your compiler until it complains about that line. Returning a pointer to something in the stack is almost universally an error, and your compiler should be able to tell you about it.

If you are going to do without std::string, I suggest you reimplement it, as something like this:

struct String {
  char *data;
  unsigned size;
  unsigned reserved;
};
Then write functions to perform all the operations you need on them.

Ah, thats a very good post. If it means a better programmer at the end of the day, then all warnings are errors.

A better way would be


char srcBuffer[MAX_LENGTH]
sprint_s(srcBuffer, MAX_LENGTH, "Hello World!");

char dstBuffer[MAX_LENGTH]

bool errorVal = doLogic(srcBuffer, dstBuffer); // return false if dstBuffer is too short

`doLogic' should also take an argument that is the length of `dstBuffer', so it can make sure to not write past the end of the buffer. Notice how `sprint_s' already does that.

Yes, that would be better to use as well for my error handling system if something went wrong so the user's application doesnt crash because of a small memory error.


char* awesome_ini::aw_subStr(const char* line, size_t start, size_t length)
{
char subbuff[AWI_MAX_KEY_NAME_LENGTH];

memset(subbuff, 0, AWI_MAX_KEY_NAME_LENGTH);

size_t p = 0;
for (size_t i = start; i < length+1; i++)
{
subbuff[p] = line[i];
p++;
}

return (char*)subbuff;
}
char subbuff is stack allocated memory, you NEVER return pointers to stack memory, this is because exactly what you are seeing is happening, instead you pass in a buffer that the function should write to(and you should pass in the size of that buffer).


The reason why you don't see this with objects is because when you return an object, you are copying the value from the function, so if i do:


struct Foo{
int x;
};

Foo Func(void){
Foo p;
p.x = 10;
return p;
}

int main(int argc, char **argv){
Foo f = Func();
printf("%d\n", f.x);
return 0;
}
what's happening is that when p is returned from func, the values of p are copied into the values of f, so your copying the values off the stack into another part of the stack that is still valid in the program context(note: this is a very simiplied explanation).

however if we do:

struct Foo{
int x;
};

Foo *Func(void){
Foo p[2];
p[0].x = 10;
p[1].x = 12;
return p;
}

int main(int argc, char **argv){
Foo *f = Func();
printf("%d\n", f[0].x);
return 0;
}
this is undefined behavior, and is very likely to crash the program, this is because you are returning a pointer into stack allocated memory that became invalid the moment you returned from Func. in many cases using the data immediately after would technically still work, but it is undefined and very bad to rely on such behavior.

one of the correct solutions is to do like so:

struct Foo{
int x;
};

bool Func(Foo *p, int psize){
if(psize<2) return false; //failed
p[0].x = 10;
p[1].x = 12;
return true; //succeded!
}

int main(int argc, char **argv){
Foo f[2];
Func(f, 2); // we can pass stack allocated memory into functions, we just should never return such memory!
printf("%d\n", f[0].x);
return 0;
}
edit:

I didn't think I would need a working example since I thought putting comments of the output would be enough.

always try to provide the minimalist working example to demonstrate your problem, you'll get more concise answers that way, then us trying to understand what the hell your doing.

Very well explained, makes sense now. I appreciate your help and everyone elses! And next time I'll be more specific so it's not a guest that code game.

Check out my open source code projects/libraries! My Homepage You may learn something.

Very well explained, makes sense now. I appreciate your help and everyone elses! And next time I'll be more specific so it's not a guest that code game.


That is an important lesson. If you have a bug you can't track down, you are probably not a good judge of where the problem is located.

One good way to proceed in these situations is to transform your program into a tiny program that does as little as possible and still shows the odd behavior. You will very often discover the bug yourself during this process, and if you don't you now have a 20-line program that you can post here, with a guarantee that you didn't leave anything important out. In the rare event that you have discovered a compiler bug, you can also send this 20-line program to the people that write your compiler.

Funny enough you said that, I do actually already do something like that.

My process...

1. Get idea.

2. Create console application in MVS.

3. Write base classes that do logic in my "main". (Keeps code clean, and easy to look at, plus I don't cause global pollution.)

4. Test in main.


//..Includes, etc...

int main(...)
{
     //For debugging..
     bool printToScreen = true;
     myClass *myPtr = new myClass("Do something cool!", printToScreen);
     myPtr->explode();

    return 0;
}

5. Import library / code to larger project.

6. Drink more coffee.

Check out my open source code projects/libraries! My Homepage You may learn something.

This topic is closed to new replies.

Advertisement