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