Avoid using low-level strings
C-style strings require too much housekeeping. Code is susceptible to bugs and memory leaks.
Problem description
You do not know when a function returns a pointer to a string, or to a copy of the string. You simply cannot trust the return value of a function.
Consider the following code, that uses a function, change_working_directory().
const char *old_dir;
const char *new_dir;
change_working_directory ("~/books/literature");
old_dir = get_working_directory();
change_working_directory ("~/books/comics");
new_dir = get_working_directory();
...
What is the string for old_dir and new_dir ?
It should be that old_dir is "~/books/literature" and new_dir is "~/books/comics".
But it actually depends on the implementation of get_working_directory().
|
typically, you can get the current working directory using this method. |
If get_working_directory() is implemented as:
const char* get_working_directory()
{
int sz = A_FILENAME_MAX;
static char *cwd = malloc(sz); // static, to avoid allocations
...
return cwd;
}
then old_dir and new_dir will be the same, namely "~/books/comics".
Which is totally counter intuitive!
If this is the way that get_working_directory() is implemented, you would have to write
const char *old_dir;
const char *new_dir;
change_working_directory ("~/books/literature");
old_dir = strdup (get_working_directory());
change_working_directory ("~/books/comics");
new_dir = strdup(get_working_directory());
...
free(new_dir);
free(old_dir);
We have guarded ourselves from unexpected behavior, taking a big hit in readability.
Unfortunately, this is not a global solution against this type of problems: the code now runs the risk of memory leaks.
What if get_working_directory() is implemented like this?
const char* get_working_directory()
{
int sz = A_FILENAME_MAX;
char *cwd = malloc(sz); // non-static
...
return cwd;
}
In this case, it is the user who is responsible of freeing the string.
const char *old_dir;
const char *new_dir;
change_working_directory ("~/books/literature");
old_dir = get_working_directory();
change_working_directory ("~/books/comics");
new_dir = get_working_directory();
...
free (new_dir);
free (old_dir);
As you can see, there’s no way you can compose the user code to guard against any kind of implementation of such functions. Even if one looks at the declaration of the function, he cannot tell whether he owns the string returned, or not.
The solution
The solution is to use C++’s string. In this case, we can implement get_working_directory() was so that it returns a string:
string get_working_directory()
{
int sz = A_FILENAME_MAX;
char *cwd = malloc(sz);
...
return string(cwd);
}
This leads to a user code which is always safe, and easy to read.
change_working_directory ("~/books/literature");
string old_dir = get_working_directory();
change_working_directory ("~/books/comics");
string new_dir = get_working_directory();
...
now, this code
- is easy to read
- requires no housekeeping (no calls to
free()are needed) - the strings returned can be changed - they are not
const - there are no surprises when it comes to calling
get_working_directory()
- tests are written to spot bugs that you know they exist, so that they do not appear again. But tests do not discover bugs. ↩