c - 正确的结构元素指针算法

c - correct struct element pointer arithmetic

我是新手 C 程序员,致力于维护一些看起来有问题的遗留嵌入式 C 代码。在以下片段中,我进行了简化:

UINT16 adcFunc(UINT8 adc, UINT8 channel)
{
    ADC_t* adc_ptr = (ADC_t*)(adc << 4);
    ADC_CH_t* adc_ch_ptr;
    adc_ch_ptr = (ADC_CH_t*)((UINT8*)&(adc_ptr->CH0) + sizeof(ADC_CH_t) * channel);
    ...
}

其中结构定义为:

typedef struct ADC_struct
{
    ...
    register8_t reserved_0x1E;
    register8_t reserved_0x1F;
    ADC_CH_t CH0;  /* ADC Channel 0 */
    ADC_CH_t CH1;  /* ADC Channel 1 */
    ADC_CH_t CH2;  /* ADC Channel 2 */
    ADC_CH_t CH3;  /* ADC Channel 3 */
} ADC_t;

指针大小为 2 个字节,UINT8 表示为 typedef unsigned char。对代码进行 linting 时,我的 linter 报告了一个警告

从 UINT8* 转换为 ADC_CH_t* 将需要的对齐方式从 1 增加到 2

adc_ch_ptr = (ADC_CH_t*)((UINT8*)&(adc_ptr->CH0) + sizeof(ADC_CH_t) * channel);

代码正在尝试计算通道指针结构中的正确偏移量 adc_ch_ptr(其中通道介于 0 和 3 之间)对我来说这似乎是一个严格的别名违规,我从中删除了强制转换(UINT8*) 毫无意义,它使应用程序崩溃了。

任何人都可以阐明如何正确计算指向正确通道的指针而不会出现混叠和 padding/alignment 问题吗?

谢谢

两个简单的解决方案是:

  • 忽略你的“linter”。保持代码不变。
  • adc_ch_ptr = (ADC_CH_t*)((UINT8*)&(adc_ptr->CH0) + sizeof(ADC_CH_t) * channel); 更改为 adc_ch_ptr = &adc_ptr->CH0 + channel;

其中任何一个都依赖于超出 C 标准要求的地址算法,并且结构没有任何奇怪的(和不必要的)填充。下面是使用严格符合 C 代码的稍微复杂的解决方案。

上面更改后的代码只是将 CH* 成员视为 ADC_CH_t 的数组;将整数 channel 添加到指向数组第一个元素(索引为 0)的指针会生成指向数组中另一个元素(索引为 channel)的指针。原始代码执行相同的运算,除了以字节为单位而不是 ADC_CH_t 类型的元素。似乎没有必要使用字节,因为元素的算术应该产生相同的结果。所以不清楚原作者为什么选择使用字节,因为生成的代码比较繁琐。

使用严格符合 C 代码的两个解决方案是:

  • 使用数组(此处定义为复合文字)查找所需地址:
    adc_ch_ptr = (ADC_CH_t *[]) {
            &adc_ptr->CH0, &adc_ptr->CH1, &adc_ptr->CH2, &adc_ptr->CH3,
        } [channel];
    
  • 使用 switch:
    switch (channel)
    {
        case 0: adc_ch_ptr = &adc_ptr->CH0; break;
        case 1: adc_ch_ptr = &adc_ptr->CH1; break;
        case 2: adc_ch_ptr = &adc_ptr->CH2; break;
        case 3: adc_ch_ptr = &adc_ptr->CH3; break;
    }
    

避免这种指针魔法,并相信编译器能够理解开关:


UINT16 adcFunc(UINT8 adc, UINT8 channel)
{
        /* this should be hidden inside a macro or an inline function*/
    ADC_t *adc_ptr = FIND_BASE_ADDRESS(adc);

    ADC_CH_t *adc_ch_ptr;

    switch (channel) {
    case 0: adc_ch_ptr = &adc_ptr->CH0; break;
    case 1: adc_ch_ptr = &adc_ptr->CH1; break;
    case 2: adc_ch_ptr = &adc_ptr->CH2; break;
    case 3: adc_ch_ptr = &adc_ptr->CH3; break;
        /* should not happen ... */
    default: return 0xffff;
        }

    /* do something with adc_ch_ptr ... */
    ...
    return something_usefull_here;
}