It seems like an error in the book - it does indeed declare new variables inside the loop. (Hint: before publishing programming books, at least compile the code first...)
But even with that embarrassing bug fixed, the code is naively written. Here's more mistakes:
- Always
const
qualify array parameters that aren't modified.
- Always use
stdint.h
in embedded systems.
- Never use magic numbers like
255
. In this case, use UINT8_MAX
instead.
The above is industry standard consensus. (Also required by MISRA-C etc.)
Also, it is most correct practice to use size_t
rather than int
for size of arrays, but that's more of a style issue remark.
In addition, a better algorithm is to have the pointers point at the min and max values found in the array, meaning we don't just get the values but also their locations in the data container. Finding the location is a very common use-case. It's about the same execution speed but we get more information.
So if we should attempt to rewrite this to some book-worthy code, it would rather look like:
void find_min_max (const uint8_t* data, size_t size, const uint8_t** min, const uint8_t** max);
Bit harder to read and use with pointer-to-pointers, but more powerful.
(Normally we would micro-optimize pointers of same type with restrict
, but in this case all pointers may end up pointing at the same object, so it isn't possible.)
Complete example:
#include <stddef.h>
#include <stdint.h>
void find_min_max (const uint8_t* data, size_t size, const uint8_t** min, const uint8_t** max)
{
*min = data;
*max = data;
for(size_t i=0; i<size; i++)
{
if(**min > data[i])
{
*min = &data[i];
}
if(**max < data[i])
{
*max = &data[i];
}
}
}
Usage example for PC: (please note that int main (void)
and stdio.h
shouldn't be used in embedded systems.)
#include <stdio.h>
#include <inttypes.h>
int main (void)
{
const uint8_t data[] = { 1, 2, 3, 4, 5, 4, 3, 2, 1, 0};
const uint8_t* min;
const uint8_t* max;
find_min_max(data, sizeof data, &min, &max);
printf("Min: %"PRIu8 ", index: %d\n", *min, (int)(min-data));
printf("Max: %"PRIu8 ", index: %d\n", *max, (int)(max-data));
return 0;
}
Disassembling this search algorithm for ARM gcc -O3:
find_min_max:
cmp r1, #0
str r0, [r2]
str r0, [r3]
bxeq lr
push {r4, lr}
add r1, r0, r1
.L5:
mov lr, r0
ldr ip, [r2]
ldrb r4, [ip] @ zero_extendqisi2
ldrb ip, [r0], #1 @ zero_extendqisi2
cmp r4, ip
strhi lr, [r2]
ldr r4, [r3]
ldrbhi ip, [r0, #-1] @ zero_extendqisi2
ldrb r4, [r4] @ zero_extendqisi2
cmp r4, ip
strcc lr, [r3]
cmp r1, r0
bne .L5
pop {r4, pc}
Not the most effective code still, very branch-intensive. I think there's plenty of room for optimizing this further, if the aim is library-quality code. But then it's also a specialized algorithm, finding both min and max, and their respective indices.
For small data sets it would probably be wiser to just sort the data first, then just grab min and max out of the sorted smallest and largest indices. If you plan to search through the data for other purposes elsewhere in the code, then definitely sort it first, so that you can use binary search.