Stop using strncpy already
randomascii.wordpress.com
randomascii.wordpress.com
That said, if the article's code would have appeared on Stack Overflow, I wouldn't be able to prevent myself from also saying:
The author's suggested replacement, the C++ template function (!) strcpy_safe(), is not so hot in my opinion.
It uses strncpy(), which is record-oriented and thus will always fill all its n bytes. If you do:
char bigbuf[1024];
strncpy(bigbuf, "foo", sizeof bigbuf);
Then strncpy() will happily 0-fill all of that buffer space, which of course is completely pointless and just a waste of precious cycles. This is because it treats the buffer as a "record", and insists to initialize all of it.This is the "flip side" of the logic that prevents it from 0-terminating if the string fills the buffer; since the buffer is a record with a known size, and not really a C string, it doesn't need to be terminated, right? Heh.
Also, POSIX reserves the namespace of functions whose names start with "str". I'm not 100% sure how well this applies to C++, but it seems prudent to avoid defining your own function with a name like that.
Yes. And that feels like a really poor choice of the function name in my opinion. It should have been strcpy_pad_with_zeros() rather than strncpy(). To reflect the fact that it is going to waste precious cache lines and memory writes.
But then, it's just C strings vs. Pascal strings all over again. It's your job as an engineer to use the right tool at the right time.
On the other end, it sucks that strlcpy is not as portable as the n counterparts. Last time I checked, the gnu libc still didn't have it for stupid bikeshed reasons.
Yep, still doesn't. Not so much for bikeshed reasons, but more for the Gnu people's stupid NIH reasons. OpenBSD offered it to them, reply was along the lines of "we don't see the need for it".
In C++ using these function is just plain nonsense. Use std::string or equivalent.
If I were writing a new code-base in C I'd standardize on using bstring but that too doesn't solve every problem.
I think storing sizes explicitly is the better solution. If what you are dealing with are actually character strings, then you might store the byte size along with the char count.
If you are only dealing with ASCII (are you sure?), you could use memcpy and avoid the strncpy zero fill behavior.
>In order to use these functions correctly you have to do this sort of nonsense.
char buffer[5];
strncpy(buffer, “Thisisalongstring”, sizeof(buffer));
buffer[sizeof(buffer)-1] = 0;
Actually, its more like this: char buffer[5] = {0};
strncpy(buffer, "This is a long string", sizeof buffer - sizeof buffer[0]);
(edit: sizeof, not sizeof()! edit2: bugfix!)But okay, maybe your compiler won't let you do that (it should). Oh. We've already touched on what Microsoft won't let you do .. maybe Microsoft won't let you do that. (In which case the answer should be: don't use Microsoft).
But anyway .. then the author says this:
>We are programmers, are we not? If the functions we are given to deal with strings are difficult to use correctly then we should write new ones.
Umm: NO! Learn to use your tools properly and stop re-inventing the wheel to fit your misunderstanding of the world! BILLIONS of lines of code out there use strncpy() and other n-fn variants, and guess what: even in safety-critical, life-threatening, embedded environments!
This article should really be titled: "If you are going to use strncpy(), and you're scared of it, THEN DON'T DEPLOY WITHOUT FULL CODE COVERAGE TESTING!"
NB: edit2 GOTCHA! Coverage, people.
Except you just failed to null-terminate your string, good job at proving the truth of the article :)
#include <stdio.h>
#include <string.h>
int main(void)
{
char buffer[5] = {0};
strncpy(buffer, "This is a long string", sizeof(buffer));
printf("%s\n", buffer);
}
$ ./test
This ^`gh? $ cat /tmp/t.c
#include <stdio.h>
#include <string.h>
char buffer[5] = {0};
int main(int argc, char argv)
{
strncpy(buffer, "This is a long string", sizeof buffer - sizeof buffer[0]);
printf("The string: [%s] The len: %ld\n", buffer, sizeof buffer);
}
$ /tmp/t
The string: [This] The len: 5
$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.6/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.6.3-1ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.6 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.6 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.6.3
edit: oops. Coverage before Coffee! The string: [This ] The len: 5
Doesn't it bother you that your 5 character long buffer that is supposed to be terminated by a null happens to have 5 characters in it "[This ]" instead of 4 visible "[This]"?Your memory just happens to have a null 6 bytes after the pointer.
char buffer[5] = {0};
strncpy(buffer, "This is a long string", sizeof buffer);
to: char buffer[5] = {0};
strncpy(buffer, "This is a long string", sizeof(buffer) - 1);
(I find sizeof() to be clearer than just sizeof in this case since "sizeof buffer - 1" looks awfully strange.(Hint, no, its an operator. Like -- and &. Do you prefer this form: --(someint);
Feel free to grep through your /usr/include for sizeof and see which way is more common.
sizeof some_struct; //computed at *compile* time
sizeof(some_struct); // also computed at *compile* time, but looks like its a runtime call
Anything that looks like something but isn't actually that thing, in my opinion - especially with a language like C - is room for programmer enlightement ..is looking unconventional. Couple of reasons... You generally want to avoid +1 / -1 in your code. You don't want to write sizeof() without parenthesis, - it reduces readability.
2. sizeof is not necessarily done at compile time. C99 allows variable-sized automatic arrays, forcing it to store and later look up the value at runtime if you use sizeof.
Real danger of C strings is that they create vulnerabilities in the network code. But unfortunately this is precisely the place, where latency is important, where everything is in C and where a solution to stop using C just wouldn't do.
Use C for great glory.
Both of you are taking an extreme position.
C isn't very difficult. However, there are a lot of nuances that you have to remember while using it. Mostly this is a matter of experience. It eventually becomes something akin to muscle memory to match every malloc() with a free(), and to always have an acute awareness of buffer sizes.
When speed is critical, using C can often be a decisive advantage. Nothing else can match the raw horsepower that an effective C programmer can bring to bear on a problem. This implies that if you don't know C, then you're handicapping yourself as a developer. Consider that most of the really excellent programmers are also excellent C programmers: cperciva, guido, antirez, tlb, rtm, zed, etc. Either all of the excellent programmers are also delusional for using C, or you're missing something important by not knowing how to be effective with C.
On the flip side, C doesn't make sense for most use cases. For example, writing a distributed system in C when you could use Go, or writing Dropbox in C when you could use Python. Most people underestimate just how fast modern CPUs are, along with underestimating how much speed you can get from effectively profiling your app written in a high-level language like Lisp. Good profiling is more valuable than changing to a lower-level language, and it's also easier in the long run.
The key is balance, and a wide perspective.