Another potential bug many might miss is the multiply by 2. On a 64-bit system where int is 32-bit (Windows for instance), it would be possible for this operation to overflow, resulting in undefined behavior.
That would mean they had to allocate 2.1GiB * sizeof(int) (or at least 1.05GiB * sizeof(int)). Even so, calling malloc on a negative integer would just have it return NULL in new_data, so the assert would fail and the program would be terminated before anything bad happens.
I think “that would require unlikely input” and “undefined behavior does nothing bad on my platform” can be dangerous when it comes to C.
For instance, given the compiler knows the capacity starts at 1 (if we fix that bug) and is always multiplied by 2, since overflowing would be undefined behavior, it can assume that will never happen and generate a shift left for the multiply. That would result in overflowing to 0 (which could trap), which when passed to malloc could (implementation defined) return a non-NULL pointer that cannot be dereferenced.
I know that’s all highly unlikely, but likely isn’t safe.
I have started learning Rust.
Having said that, my attempt at a safer C implementation is here. It uses structure marking, checks the alignment of arguments, accounts for unsigned overflow, and will detect some uses after frees. Vec is an incomplete type in order to hide the implementation. A production version would return an error code instead of exiting the program.
If you are using C, C++, or are writing system software then Paranoid Programming - Techniques for Constructing Robust Software (HN discussion) is a great presentation of techniques that you can use to improve the reliability of your code.
Edited to improve wording and add a link for incomplete types
Oh yes! Paranoid Programming was a great write-up with piles of techniques for improving reliability, especially those used in Tandem and Stratus. They’re five 9’s systems for any readers not aware of them.
The point of this article is pretty badly made. They designed a purposely broken vector data structure and operations in C disregarding many mistakes a mediocre C programmer would have either known to avoid from good practice or just by knowing them, and then proposed a solution by making an implementation in rust that they actually put some effort in, because rust does force bad programmers to put more effort into their program before it can compile. Was that what they were trying to show?
agreed, I would have expected a case study to go find some real world equivalent code written in each and then look for problems.
How about another bug, int*2 is an undefined overflow. That’ll certainly cause problems.
This is one area where Rust and C are different; overflow is well-defined in Rust.