指针和正则变量同名

Pointer and regular variable with the same name

在 "Embedded C programming" 一书的 "Memory and pointers" 章节中,Mark Siegesmund 给出了以下示例:

void Find_Min_Max( int list[], int count, int * min, int * max){
    for( int i=0, min=255, max=0; i<count; i++){
        if( *min>list[i])
            *min=list[i];
        if( *max<list[i] )
            *max=list[i];
    }
}
// call like this: Find_Min_Max( table, sizeof(table), &lowest, &highest);

如果我理解正确的话:

(不能 100% 确定最后一个。)

在 for 循环中,他每次都将数组列表中的下一个 int 与指针 *min 和 *max 地址处的内容进行比较,并在必要时更新这些地址处的值。

但是在循环的定义中,他定义了min = 255, max = 0。

在我看来,这是两个全新的变量,还没有被初始化。 该行不应该是

for( int i=0, *min=255, *max=0: i<count; i++){

这是书上的错误还是我理解有误?

这是代码中的一些错误。也许它应该是:

for( int i=0, *min=255, *max=0; i<count; i++){
        if( *min>list[i])
            *min=list[i];
        if( *max<list[i] )
            *max=list[i];
    }

int i=0, min=255, max=0int i=0, *min=255, *max=0都定义了三个新变量,初始化了,但是在循环体中使用不正确

应在循环之前初始化限制:

*min=255;
*max=0;
for(int i=0; i<count; i++)

或者,可以在循环之前定义新变量 i,但这不像第一个那样容易阅读:

int i;
for(i=0, *min=255, *max=0; i<count; i++)

请注意,如果有小于0或大于255的值,返回的最小值和最大值将不正确。

这似乎是书中的错误 - 它确实在循环内声明了新变量。 (提示:在出版编程书籍之前,至少先编译代码……)

但即使修复了那个令人尴尬的错误,代码还是天真地编写的。这里还有更多的错误:

  • 始终const限定未修改的数组参数。
  • 在嵌入式系统中始终使用 stdint.h
  • 永远不要使用像 255 这样的幻数。在这种情况下,请改用 UINT8_MAX

以上为行业标准共识。 (MISRA-C 等也需要)

此外,对于数组的大小,使用 size_t 而不是 int 是最正确的做法,但这更像是一个风格问题评论。

此外,更好的算法是让指针指向数组中找到的最小值和最大值,这意味着我们不仅可以获取值,还可以获取它们在数据容器中的位置。查找位置是一个非常常见的用例。执行速度大致相同,但我们获得了更多信息。


因此,如果我们应该尝试将其重写为一些值得一书的代码,它更像是:

void find_min_max (const uint8_t* data, size_t size, const uint8_t** min, const uint8_t** max);

有点难以阅读和使用指针到指针,但更强大。

(通常我们会用 restrict 对相同类型的指针进行微优化,但在这种情况下,所有指针最终可能指向同一个对象,所以这是不可能的。)

完整示例:

#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];
    }
  }
}

PC 用法示例:(请注意 int main (void)stdio.h 不应在嵌入式系统中使用。)

#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;
}

为 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}

仍然不是最有效的代码,非常分支密集。如果目标是库质量代码,我认为还有很大的空间可以进一步优化。但它也是一个专门的算法,找到最小值和最大值,以及它们各自的索引。

对于小数据集,先对数据进行排序可能更明智,然后从排序后的最小和最大索引中获取最小值和最大值。如果你打算在代码的其他地方搜索数据用于其他目的,那么一定要先排序,这样你就可以使用二分查找了。