Not even necessarily the end of the function.
True! But I worded it that way because if one were to only add
array++ before the existing loop, then that loop would end up accessing one element beyond the array; a buffer overrun of the off-by-one type.
Your versions change the loop too, and do the right thing, obviously.
I often write such functions this way:
void add_two(int *array, const size_t size)
{
int *const end = array + size;
while (array < end)
*(array++) += 2;
}
Here,the two
consts are mostly for us humans to see the intention that the code will not try to change the values. Theoretically, it might help some C compilers to generate better code, but all the current compilers seem to optimize this kind of code just as well without the extra const qualifiers. However, I find them informative for myself.
size_t is the proper type for sizes in memory (or
ssize_t if you need negative values too). On ILP64 architectures (64-bit linux),
int is only 32 bits, whereas pointers and
size_t are 64-bit. This allows you to use arrays (and memory regions) with more than four short billion (2
32) elements.
GCC tends to generate better code when using pointers instead of array indexing on some architectures. Not a big difference, but I like it.
The parentheses around
(array++) are there only to remind the human programmer that the expression dereferences the
array pointer (and afterwards increments the pointer, to point to the next element in the array), adds two to the dereferenced value: thus adding two to the array value,
None of these are any kind of hard rules – or always true! – but it is the "coding style" that has served me well.
The key point I'd like to emphasize here is how to read pointer types, as I explained at the end of my previous post. It clarifies the use of such pointers. An additional technique I've used is to add parentheses according to the C operator precedence rules when there is a possibility of ambiguity.
Even when you see famed three-star code (
const char *volatile *const *const ptr), you can immediately read it in human terms (
ptr is a const, a pointer to a const, a pointer to a volatile, a pointer to a const char), meaning the pointer itself and the pointer it points to are not changed by the current code (except if via casts), but the pointer that points to can (it being
volatile meaning code not visible in this code may modify it at any point in time, so the compiler must not cache its value, and instead has to use its current value in each expression), and that points to constant character data, usually a string possibly in read-only memory.
Here is a real world example of code I do actually use: this is a POSIX low-level async-signal safe write() function that returns zero if the entire data was successfully written, or an errno code otherwise, while keeping errno code unchanged:
static inline int write_all(const int fd, const void *const src, const size_t len)
{
const char *ptr = (const char *)src;
const char *const end = (const char *)src + len;
ssize_t len;
int saved_errno, retval;
if (fd == -1)
return EBADF;
if (len < 1)
return 0;
if (!src)
return EINVAL;
saved_errno = errno;
retval = 0;
while (ptr < end) {
n = write(fd, ptr, (size_t)(end - ptr));
if (n > 0) {
ptr += n;
} else
if (n != -1) {
retval = EIO;
break;
} else
if (errno != EINTR) {
retval = errno;
break;
}
}
errno = saved_errno;
return retval;
}
It is particularly useful when experimenting with (POSIX) signals and (POSIX) signal handlers. Signal handlers are special functions that are called when a signal is delivered to a process (or to a specific thread within a process; usually the kernel just picks one thread that does not currently block that signal). Within a signal handler, only
async-signal safe functions can be safely called; using any other function (including
printf() and so on) can produce unexpected effects. (And it is
not enough to assume that if they work for you in one situation, that pattern works in others, because there are complex asynchronous dependencies here. There are only a couple of standard C functions whose async-signal safeness is still being discussed, because some of the implementations are and others are not.)
However, for signal handlers, one can use the following
wrerr() wrappers:
static int wrerr(const char *msg)
{
if (msg) {
const char *end = msg;
while (*end)
end++;
return write_all(STDERR_FILENO, msg, (size_t)(end - msg));
} else
return 0;
}
static int wrerrl(const long value)
{
char buffer[32];
char *ptr = buffer + sizeof buffer;
unsigned long u = (value < 0) ? -value : value;
do {
*(--ptr) = '0' + (u % 10);
u /= 10;
} while (u);
if (value < 0)
*(--ptr) = '-';
return write_all(STDERR_FILENO, ptr, (size_t)(buffer + (sizeof buffer) - ptr));
}
so that one can safely explore POSIX signal handling using e.g.
void my_signal_handler(int signum)
{
wrerr("Received signal ");
wrerrl(signum);
wrerr(".\n");
}
or, if installed with SA_SIGINFO flag,
void my_signal_handler(int signum, siginfo_t *info, void *context)
{
wrerr("Process ");
wrerrl(getpid());
wrerr(" received signal ");
wrerrl(signum);
if (info->si_pid) {
wrerr(" from process ");
wrerrl(info->si_pid);
wrerr(".\n");
} else
wrerr(" from the kernel.\n");
}
On Linux, the variant
pid_t gettid(void) { return syscall(SYS_gettid); }
void my_signal_handler(int signum, siginfo_t *info, void *context)
{
wrerr("Process ");
wrerrl(getpid());
wrerr(" task ");
wrerrl(gettid());
wrerr(" received signal ");
wrerrl(signum);
if (info->si_pid) {
wrerr(" from process ");
wrerrl(info->si_pid);
wrerr(".\n");
} else
wrerr(" from the kernel.\n");
}
because this latter will also tell you the task ID (similar purpose as
pthread_t, but different numerical values), and show in practice how signals can be delivered to signal handlers using essentially a random thread in a process. (Well, not "random" in the sense of random numbers, but in the sense that there are no guarantees, unless you block the signal in all but one thread. Which is common to do.)
Oops, sorry for the wall of text.