Hacker News new | ask | show | jobs
by augustk 1115 days ago
Even without array bounds checking, a bit of discipline and smart conventions will go a long way of reducing errors:

1. Define a macro function for retrieving the length of an array:

  #define LEN(arr) (sizeof (arr) / sizeof (arr)[0])
2. Don't introduce macro constants for array lengths; hard code the length in the declaration and use LEN to retrieve it. Example:

  int a[100];
  ...
  for (i = 0; i < LEN(a); i++) {
     ...
  }
3. Define a macro function for dynamic array allocation:

  #define NEW_ARRAY(ptr, n) \
     (ptr) = malloc((n) * sizeof (ptr)[0]); \
     if ((ptr) == NULL) { \
        fprintf(stderr, "Memory allocation failed: %s\n", strerror(errno)); \
        exit(EXIT_FAILURE); \
     }
4. When you create a function with an array argument, also add an argument for the array length.

5. Use a convention for naming the length of array pointer targets, for instance by adding the suffix `Len'. Example:

  int *b, bLen = 100;
  ...
  NEW_ARRAY(b, bLen);  /* nice to know that b and bLen belong together */
  ...
  SomeFunction(b, bLen, ...);
  ...
  for (i = 0; i < bLen; i++) {
    ...
  }
 
6. Define your own safe wrappers around unsafe standard library functions or use someone else's code that does that.
5 comments

The issue with 1 is that it only works until you pass an array into a function by pointer, then the macro no longer works.

In my experience it's most likely that a function will write past the bounds of a buffer that's been passed as an argument. In that case, make sure the size of array is always included as an argument as you said in 4.

> The issue with 1 is that it only works until you pass an array into a function by pointer, then the macro no longer works. GCC even has a warning for this.

Even worse, even if you specify the argument to be "of the type" array, it will actually still decay to a pointer. Basically, this macro will only work if you use it in the same function the array is defined.

https://godbolt.org/z/vr4za73qq

You need to pass a pointer to an array: https://godbolt.org/z/jYzY79ac4

When passing an array it decays into a pointer and the size is lost. We can also change sizeof to recover it, but there was a proposal for a _Lengthof operator which could work here.

One exception is if you explicitly define argument as array of fixed length.

Downside being, obviously, that it will only work with arrays of that particular length.

see item 4
Your allocation macro can lead to heap underflows if the multiplication wraps around. Which can definitely be exploitable.

You should either add overflow checking to the macro or even better just use the damn libc api and call calloc. Or if you really insist on avoiding zeroing overhead, there's reallocarray(NULL, ...) if you use a reasonably modern libc.

You could extend point 1. by making a convention of always declaring pointers to arrays like so:

  int (*data)[datalen];
This requires you to dereference it once to get an array, then dereference it a second time to get a value. The advantage is that the array value can be used the same as an normal array on the stack, including passing it to the array length macro you describe.
Isn't this exactly what the fine article does?
Nice! I don't like how C has null terminated char arrays plays with this. Ideally this would somehow enforce a guard null byte at the end of each array not included in the size.
> bit of discipline