Post subject: Simple ways to avoid common memory management issues in C
Emulator Coder
Joined: 3/9/2004
Posts: 4588
Location: In his lab studying psychology to find new ways to torture TASers and forumers
Here's an interesting article which explains some simple techniques to avoid many common memory management problems in C without much effort at all.
Warning: Opinions expressed by Nach or others in this post do not necessarily reflect the views, opinions, or position of Nach himself on the matter(s) being discussed therein.
Joined: 2/18/2010
Posts: 156
Location: home
Neat article, should come in handy for my future C programming ventures. Memory management isn't exactly my strongest point in C.
My user name is rather long, feel free to call me by htwt or tape.
Tub
Joined: 6/25/2005
Posts: 1377
pointer nulling will help prevent that one elusive bug, but std::unique_ptr and exceptions for OOM handling will prevent hundreds. Just saying.
m00
Player (36)
Joined: 9/11/2004
Posts: 2623
Tub wrote:
pointer nulling will help prevent that one elusive bug, but std::unique_ptr and exceptions for OOM handling will prevent hundreds. Just saying.
That would help if he were making a C++ tutorial. ;)
Build a man a fire, warm him for a day, Set a man on fire, warm him for the rest of his life.
Emulator Coder
Joined: 3/9/2004
Posts: 4588
Location: In his lab studying psychology to find new ways to torture TASers and forumers
Tub wrote:
pointer nulling will help prevent that one elusive bug
Just one? Use after free. Double free. Anything derived from the above.
Tub wrote:
but std::unique_ptr and exceptions for OOM handling will prevent hundreds. Just saying.
Not sure how you get to hundreds unless you're including every derived problem. std::unique_ptr is rarely the best solution though, as very often, you'll be better served with an std::deque, std::vector, std::string, or something along those lines. In a ~25,000 line library I have, new is only called in two places. Also, don't forget about std::shared_ptr.
Warning: Opinions expressed by Nach or others in this post do not necessarily reflect the views, opinions, or position of Nach himself on the matter(s) being discussed therein.
Player (36)
Joined: 9/11/2004
Posts: 2623
Nach wrote:
Not sure how you get to hundreds unless you're including every derived problem. std::unique_ptr is rarely the best solution though, as very often, you'll be better served with an std::deque, std::vector, std::string, or something along those lines. In a ~25,000 line library I have, new is only called in two places. Also, don't forget about std::shared_ptr.
If you're already on the C++14 bandwagon, make_unique is a thing, no more need for new.
Build a man a fire, warm him for a day, Set a man on fire, warm him for the rest of his life.
Emulator Coder
Joined: 3/9/2004
Posts: 4588
Location: In his lab studying psychology to find new ways to torture TASers and forumers
OmnipotentEntity wrote:
If you're already on the C++14 bandwagon, make_unique is a thing, no more need for new.
Sounds useful. I imagine there's a make_shared as well? In any case, I'm not jumping on any such bandwagon till the standard (or specific parts of it) is officially approved. I hate having to rewrite something because of last minute changes.
Warning: Opinions expressed by Nach or others in this post do not necessarily reflect the views, opinions, or position of Nach himself on the matter(s) being discussed therein.
Player (36)
Joined: 9/11/2004
Posts: 2623
Nach wrote:
OmnipotentEntity wrote:
If you're already on the C++14 bandwagon, make_unique is a thing, no more need for new.
Sounds useful. I imagine there's a make_shared as well?
make_shared was a thing in C++11. C++14 added make_unique because it was an oversight, and they originally intended to. Using make_shared and make_unique are generally advised for performance reasons because of cache locality (ie, they can use just one call to new, to contain both the object being held and all of the various doodads that the pointer class needs, rather than taking some object that's already been newed somewhere, and newing somewhere else the pointer information.) C++14 is near or at its final draft, and because make_unique is considered a correction of a defect in C++11, I don't see it going away in any quantum reality.
Build a man a fire, warm him for a day, Set a man on fire, warm him for the rest of his life.
Tub
Joined: 6/25/2005
Posts: 1377
Nach wrote:
Just one? Use after free. Double free. Anything derived from the above.
I was counting instances of bugs, not types of bugs. I can't remember when I last had a double free bug in my code (or 3rd party library code). Usually, the free'd pointer goes out of scope anyway so it isn't much of an issue. YMMV of course. Note though that unique_ptr prevents both types of bugs you mentioned. You present a case where pointer nulling is absolutely required, and that's in functions which allocate and return a new object, but have multiple failure conditions. Manual cleanup of the already allocated resources is an error prone process, and nulling of the return pointer is important. However, the manual cleanup code disappears once destructors, unique_ptr and friends appear; just throw an exception or return nullptr and all is well. What I was getting at: memory and error handling in C is a pain. Advising meticulous pointer nulling and error checking seems weird when there are better alternatives available, that can do the job with less code complexity (=fewer possibilities for human error), equal memory consumption and better performance. And I'm not aware of many platforms where C compilers exist, but not C++ compilers.
Nach wrote:
std::unique_ptr is rarely the best solution though, as very often, you'll be better served with an std::deque, std::vector, std::string, or something along those lines. In a ~25,000 line library I have, new is only called in two places. Also, don't forget about std::shared_ptr.
Each tool serves a purpose. For functions that return a pointer to a newly allocated object (like your example), unique_ptr fits the bill. (Or, in your special case, std::string)
OmnipotentEntity wrote:
Using make_shared and make_unique are generally advised for performance reasons because of cache locality (ie, they can use just one call to new, to contain both the object being held and all of the various doodads that the pointer class needs, rather than taking some object that's already been newed somewhere, and newing somewhere else the pointer information.)
That's true for shared pointer, since the reference counter and the object get allocated at the same memory location. unique_ptr does not have a reference counter or any other memory overhead (it's just a pointer!), and make_unique does not improve performance. There are three reasons make_unique is useful: * For exception safety in certain situations: http://herbsutter.com/gotw/_102/ * for brevity, auto x = std::make_unique<MyClass>(1, 2, 3) is shorter than std::unique_ptr<MyClass> x( new MyClass(1, 2, 3) ); * for consistency with make_shared etc.
OmnipotentEntity wrote:
C++14 is near or at its final draft, and because make_unique is considered a correction of a defect in C++11, I don't see it going away in any quantum reality.
If it should go away, it's a template function with 5 lines of code and thus trivial to reimplement.. all you'd need to do is refactor it to a private namespace.
m00
Banned User, Former player
Joined: 3/10/2004
Posts: 7698
Location: Finland
If you are using std::unique_ptr (or std::shared_ptr for that matter), it usually means that you are allocating individual objects dynamically. While there are some contexts in which this is both practically inevitable but also quite useful (mainly in contexts where you have an inheritance hierarchy and code needs to be able to handle objects of different types; an example of this would be a GUI library), in my quite many years of experience in C++ programming I have found this exceedingly rare. (One reason for this might be that I do mostly game programming, and having to write generic code for an inheritance hierarchy is surprisingly rare there. Which is a good thing, really, since 'new' and 'delete' are quite heavy operations.) In the vast majority of cases the standard containers do just fine. And in fact, if you use std::vector (and sometimes std::deque) your code will be much more efficient than if you allocated all objects individually. (Sometimes std::set and std::map are very useful, and they do allocate each element individually, which is a bummer, but they are extremely useful data containers in certain situations, so that makes up for it, especially if you are aware of the overhead of allocation and try to avoid doing it in tight inner loops.) I don't even remember when was the last time I wrote a 'new' or 'delete'. Must be years.
Emulator Coder
Joined: 3/9/2004
Posts: 4588
Location: In his lab studying psychology to find new ways to torture TASers and forumers
OmnipotentEntity wrote:
Nach wrote:
OmnipotentEntity wrote:
If you're already on the C++14 bandwagon, make_unique is a thing, no more need for new.
Sounds useful. I imagine there's a make_shared as well?
make_shared was a thing in C++11. C++14 added make_unique because it was an oversight, and they originally intended to.
Thanks for the info, for some reason, that's not listed in the C++ material I have.
OmnipotentEntity wrote:
C++14 is near or at its final draft, and because make_unique is considered a correction of a defect in C++11, I don't see it going away in any quantum reality.
My statement was in general, I avoid something till it's standardized. If something is really important and I know I can reimplement it easily if a particular platform lacks it, or it goes away, I'll use it too.
Tub wrote:
Note though that unique_ptr prevents both types of bugs you mentioned.
I'm fully aware.
Tub wrote:
You present a case where pointer nulling is absolutely required, and that's in functions which allocate and return a new object, but have multiple failure conditions. Manual cleanup of the already allocated resources is an error prone process, and nulling of the return pointer is important. However, the manual cleanup code disappears once destructors, unique_ptr and friends appear; just throw an exception or return nullptr and all is well. What I was getting at: memory and error handling in C is a pain. Advising meticulous pointer nulling and error checking seems weird when there are better alternatives available, that can do the job with less code complexity (=fewer possibilities for human error), equal memory consumption and better performance. And I'm not aware of many platforms where C compilers exist, but not C++ compilers.
I think we all agree that C++ offers better facilities for proper safe memory management. However, C is still extremely popular, and there are many C only projects which need good solutions for managing memory well. C is still the most popular language. And many a project (Linux, the BSDs, OpenSSL, cURL, zlib, SQLite, ...) will not allow for C++ in the core libraries.
Tub wrote:
Nach wrote:
std::unique_ptr is rarely the best solution though, as very often, you'll be better served with an std::deque, std::vector, std::string, or something along those lines. In a ~25,000 line library I have, new is only called in two places. Also, don't forget about std::shared_ptr.
Each tool serves a purpose. For functions that return a pointer to a newly allocated object (like your example), unique_ptr fits the bill. (Or, in your special case, std::string)
Yes, I agree that each thing has a good purpose, and I use all of them, but which case did I mention that unique_ptr fits the bill?
Warp wrote:
I don't even remember when was the last time I wrote a 'new' or 'delete'. Must be years.
The only time I found where it's best to use some concept of new is where you have a function which needs to return an object. This is then further limited that not all cases which need to return an object, but only two of them: 1) Allow for the possibility that there's no object to return (exceptions aren't always the best approach for a failure scenario). 2) You have a virtual function returning some instance via a base pointer. And in both of those cases, std::unique_ptr and std::shared_ptr are your friends. In virtually every other scenario, STL containers or direct/on-stack/local objects would generally be the more correct approach.
Warning: Opinions expressed by Nach or others in this post do not necessarily reflect the views, opinions, or position of Nach himself on the matter(s) being discussed therein.
Tub
Joined: 6/25/2005
Posts: 1377
Nach wrote:
Yes, I agree that each thing has a good purpose, and I use all of them, but which case did I mention that unique_ptr fits the bill?
I was refering to asprintf from your blog post. Or anything else that would be called a "factory" in java or a "source" in c++. Returning std::unique_ptr<Object> instead of Object * makes ownership transfer explicit. It also defaults the calling function to deallocate the object, requiring special code to keep it alive, instead of the other way around.
m00
Emulator Coder
Joined: 3/9/2004
Posts: 4588
Location: In his lab studying psychology to find new ways to torture TASers and forumers
Unfortunately, a compliant asprintf() cannot use C++. If this was C++, the right approach would be to printf() into an std::string. My std::string implementation actually has a printf() method, as well as a way to build up a string from anything without silly format instructions.
Warning: Opinions expressed by Nach or others in this post do not necessarily reflect the views, opinions, or position of Nach himself on the matter(s) being discussed therein.
Banned User, Former player
Joined: 3/10/2004
Posts: 7698
Location: Finland
Nach wrote:
The only time I found where it's best to use some concept of new is where you have a function which needs to return an object.
The last time I remember using 'new' was when I wanted to implement an array that used copy-on-write, as that was the most efficient solution to a particular problem I was implementing. (Naturally the 'new' and 'delete' were encapsulated inside the array class.) Then there was that time that I implemented a data container that used the same technique as "short string optimization" (because in the vast majority of cases the amount of data was so little that it would fit inside the object itself, and rarely was there need to actually allocate extra memory, but the class needed to be able to support any amount of data.) Those are the most recent ones I remember (and it has been some time.)
Emulator Coder
Joined: 3/9/2004
Posts: 4588
Location: In his lab studying psychology to find new ways to torture TASers and forumers
Those are both very good examples. I guess I should amend my statement by saying it also makes sense for creating your own containers. But often in those cases it is optimal to use an allocator.
Warning: Opinions expressed by Nach or others in this post do not necessarily reflect the views, opinions, or position of Nach himself on the matter(s) being discussed therein.
Player (36)
Joined: 9/11/2004
Posts: 2623
Nach wrote:
Unfortunately, a compliant asprintf() cannot use C++. If this was C++, the right approach would be to printf() into an std::string. My std::string implementation actually has a printf() method, as well as a way to build up a string from anything without silly format instructions.
Have you taken a look at the tinyformat library? https://github.com/c42f/tinyformat
Build a man a fire, warm him for a day, Set a man on fire, warm him for the rest of his life.
Emulator Coder
Joined: 3/9/2004
Posts: 4588
Location: In his lab studying psychology to find new ways to torture TASers and forumers
OmnipotentEntity wrote:
Nach wrote:
Unfortunately, a compliant asprintf() cannot use C++. If this was C++, the right approach would be to printf() into an std::string. My std::string implementation actually has a printf() method, as well as a way to build up a string from anything without silly format instructions.
Have you taken a look at the tinyformat library? https://github.com/c42f/tinyformat
No.
Warning: Opinions expressed by Nach or others in this post do not necessarily reflect the views, opinions, or position of Nach himself on the matter(s) being discussed therein.