Guide to Advanced Programming in C
pfacka.binaryparadise.com
pfacka.binaryparadise.com
int create_vector(struct vector *vc, int size) {
vc->data = 0;
vc->size = 0;
if (vc == NULL) {
return VECTOR_NULL_ERROR;
}
/* check for integer and SIZE_MAX overflow */
if (size == 0 || size > SIZE_MAX) {
errno = ENOMEM;
return VECTOR_SIZE_ERROR;
}
Accessing the fields of vc before the NULL check seems like an error. Also, would it not be simpler to change type of the size parameter to size_t so that it's the same type as the size field of the vector struct? int8_t i;
/* ... */
i += 1;
if (i == 0) {
/* ... */
} int incr(int i) {
int old = i;
i += 1;
if (i < old) return 1;
return 0;
}
GCC optimizes this to 'return 0;'.EDIT: Updated to a better example.
int zero1(int* i) {
*i = 0;
return 0;
}
int zero2(int* i) {
*i = 0;
if(i == 0) return 1;
return 0;
}
GCC produces the same code for these two functions. If the 'if' in zero2 is moved to the top of the function, it takes effect.I tried this on GCC 4.8.1. A wonderful site for checking these things is: http://gcc.godbolt.org/
Regardless of optimisation, it will never be followed - it segfaults if NULL and returns normally if not.
I found you can control this with -f{no-,}delete-null-pointer-checks. Apparently it's enabled by -O2.
http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options...
I'm not describing some optimization that you, the programmer, can do. I'm talking about what the compiler can (and will) do by assuming your code doesn't have undefined behavior.
In this particular case, the compiler recognizes that if the pointer is NULL, undefined behavior has already been invoked and it can therefore eliminate the check.
I recommend this series of posts on the LLVM blog: http://blog.llvm.org/2011/05/what-every-c-programmer-should-...
ah, makes sense thank you !
*you will see a segfault*
No, you will see undefined behavior. Undefined behavior doesn't mean "you get a segfault". That would be defined behavior. newptr = realloc(vc->data, newsize);
should be changed to: newptr = realloc(vc->data, newsize * sizeof(int));What should be done in case vc is NULL? Currently it almost certainly causes a segfault, but I've seen people insist on returning an error code when a NULL argument is given but that might go unchecked. A segfault is easy to debug, a forgotten error code is not.
int poke_foo(struct foo *foo) {
// if you're writing a library, this is asking for trouble...
if(foo == NULL)
return -1; // eventually someone forgets to check for this
foo->bar += 1;
return 0;
}
The only sensible thing you can do is add an assert. That will stop the program when the error happens and make it easy to debug. int poke_foo(struct foo *foo) {
assert(foo != NULL); // asseting here...
foo->bar += 1; // ... is not much better than segfaulting here
return 0;
}
But in the end, we're still reliably crashing the program if NULL is passed and it is as easy to debug as a segfault would. The assert may keep your static analysis tool (like Coverity) happy but it won't really make a huge difference.NULL pointers are generally not very difficult to debug, what is hard is dealing with free()'d pointers and other non-null invalid pointer values.
I'm on a weird embedded platform where segfaults are almost impossible to debug; the only way to get information out is a log facility over USB that loses the most recent lines of log when it segfaults and resets :(
Similar problems may haunt you when working in kernel space.
But still, null pointers are easy compared to other invalid pointers that can not be distinguished by their value.
Best practices in C are not as easy to make as in other languages because the environment the code runs in may be anything from a micro controller to an embedded platform to kernel space to modern user space apps. It's not so much about the language as it is about the environment the code runs in.
I recently revisited some example code I wrote many years ago [1], and found some new issues, and there are most likely still some I haven't noticed.
That being said, when you use a title like this article's, it is a good idea to check your example code thoroughly.
"What happens is that variable i is converted to unsigned integer." No: 'long i' is converted to 'unsigned long'.
"Usually size_t corresponds with long of given architecture." No: For example, on Win64 size_t is 64 bits whereas long is 32 bits.
If you're going to write about Advanced Programming, you should be careful to actually be correct.
> "What happens is that variable i is converted to unsigned integer." No: 'long i' is converted to 'unsigned long'.
Actually, unsigned long is an unsigned integer. He didn't write unsigned int.
> "Usually size_t corresponds with long of given architecture." No: For example, on Win64 size_t is 64 bits whereas long is 32 bits.
"Usually" is the keyword here. He could have said "Usually size_t has at least the same amount of bits as long" and it would be better related to the referred rule.
if ( size == 0 || size > SIZE_MAX ) {...
size is not given a type, but if it is a size_t, then the comparison is constant and if statements is always false, unless size == 0. The second part is useless, since the maximum size of size_t == SIZE_MAX.This, in fact, was the source of a vulnerability in PHP [1]. (The way they 'resolved' it, uh, isn't much better.)
There is almost never a magic bullet for integer overflow. Programming secure systems requires thought.
[1] http://use.perl.org/use.perl.org/_Aristotle/journal/33448.ht...
realloc(valid_pointer,0);
IS defined (in C89 no less) to act like free(). It is NOT operating system dependent.Still, I agree with your general sentiment. That some of these are "advanced" topics is troubling. Too many programmers today don't know how a computer works. Our ideas of "mastery" are way too low.
For instance, I don't think it's a good idea to recommend using Boehm GC as a general solution to avoid memory management. It can't really tell the difference between pointers and pointer-sized integers, which means it usually leaks memory.
Citation needed.
Integers matching valid pointers to allocated objects are rare - only common on 32-bit when running near limits of virtual memory space. Which is a bad idea anyway.
Language runtimes that eventually move off Boehm, such as Mono, usually cite other reasons.
There are (were) a couple bugs in the example code. Here are a some guidelines that will help avoid those, and most problems with integer operations and the heap in general.
First, don't mix unsigned and signed types in arithmetic; and always prefer the size_t type for variables representing the size of an object.
Second, check for overflow before an operation, not after, like so:
if (size > SIZE_MAX / 2) {
goto error;
}
newsize = size * 2;
Third, always double-check the arguments to memory allocation functions, especially for zero, because the result is not always well defined. if (size >= SIZE_MAX - n) {
goto error;
}
foo = malloc(size + n);
foo[size] = ...; if (size && size > SIZE_MAX) {
errno = ENOMEM;
err(1, "overflow");
}
is a total no-op. It can't ever be true, the compiler might as well just remove the whole thing.(Not your fault. I too miss the good-old-days when copying a PDF link from Google didn't involve multiple steps or URL decoding.)
For many kind of programs reference counting is the way to go for C, it still is manual, but an order of magnitude safer...
1. In case of NULL pointer free does no action.
2. In "double free corruption" section, it says "Could be caused by calling free with pointer, which is [..] NULL pointer"
So: which is it? Otherwise, there's no point in the NULL/assert() dance, you can freely free() with impunity: struct foo *a, *b, *c;
a=NULL; b=NULL; c=NULL;
a=malloc(sizeof *a);
b=malloc(sizeof *b);
c=malloc(sizeof *c);
if(!(a && b && c)) {free(a); free(b); free(c); return 1;}Calling free() on NULL is a no-op.
EDIT: Found the answer (thanks, draft C11 standard.)
Section 6.3.2.1 contains the definition of lvalue, and it does not use the phrase "locator value" at all. However if you want to use it as a reminder that modifiable lvalues can be assigned to, then more power to ya :-)
I can't help it - I'm a sucker for standardese.
I really strongly disagree with this. Better for it to crash if you expect this memory to always have been allocated. This way you can fix your bug instead of putting your app into some potentially unexpected state... if allocations fail it doesn't make sense to just 'carry on anyway' in many situations.
Otherwise, you need a way for the cleanup code to dispose of the resources and leave the system in a consistent state before crashing.
Ok, its obviously good practice to develop your software in a way that a random outage doesn't leave anything in an inconsistent state, but a lot of software fails to do this. At the very least, you may end up with something like when a server crashes and cannot restart because the port is still considered in use by the OS.
Imagine the customer error report or imagine its a critical system and it starts making mistakes...
For most software smoke testing heavily is enough to make it super rock solid. I know that most software does not do this - just using a web browser or a smartphone makes it painfully obvious that even the big software houses have some seriously shoddy practices and that testing gets seriously neglected (it may be that its impractically big... i stuggle to buy that tbh)
__m128i c = _mm_set1_epi16(2) __attribute__((aligned(16)));
Don't gcc/clang take care of aligning that type automatically? Isn't the attribute thus redundant?
GCC uses it.
edit: also Inkscape, w3m.