简体   繁体   中英

c - correct struct element pointer arithmetic

I'm newbie C programmer working on maintaining some legacy embedded C code that looks problematic. In the following snippets I have simplified:

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);
    ...
}

Where the structure definition is given as:

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;

With the pointer size being 2 bytes and UINT8 represented as a typedef unsigned char . When linting the code, my linter reports back a warning

cast from UINT8* to ADC_CH_t* increases required alignment from 1 to 2 on the line

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

The code is trying to calculate the correct offset into the struct for the channel pointer adc_ch_ptr (where channel is between 0 and 3) It looks like a strict aliasing violation to me and I removed the cast from (UINT8*) senselessly and it crashed the application.

Can anyone shed some light on how to correctly calculate the pointer to the correct channel without aliasing and padding/alignment issues?

Thanks

Avoid this pointer magic, and trust the compiler to understand the switch:


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

Two simple solutions are:

  • Ignore your “linter”. Leave the code as it is.
  • Change adc_ch_ptr = (ADC_CH_t*)((UINT8*)&(adc_ptr->CH0) + sizeof(ADC_CH_t) * channel); to adc_ch_ptr = &adc_ptr->CH0 + channel; .

Either of these relies on the address arithmetic working beyond what the C standard requires and the structure not having any weird (and unnecessary) padding. Slightly more complicated solutions using strictly conforming C code are below.

The changed code above simply treats the CH* members as if they were an array of ADC_CH_t ; adding an integer channel to a pointer to the first element (with index 0) of an array produces a pointer to another element in the array (with index channel ). The original code does the same arithmetic except in units of bytes instead of elements of type ADC_CH_t . It appears unnecessary to use bytes, as the arithmetic with elements should produce the same results. So it is unclear why the original author chose to use bytes, given that the resulting code is more cumbersome.

Two solutions that use strictly conforming C code are:

  • Use an array (defined here as a compound literal) to look up the desired address:
     adc_ch_ptr = (ADC_CH_t *[]) { &adc_ptr->CH0, &adc_ptr->CH1, &adc_ptr->CH2, &adc_ptr->CH3, } [channel];
  • Use a 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; }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM