предупреждение -Wlto-type-mismatch и структура с flexible array

b707
Offline
Зарегистрирован: 26.05.2017

продолжая копать аддон Кларка для СТМ32, наткнулся на такую проблему...

остальное в следующем сообщении, чтобы можно было редактировать ошибки

b707
Offline
Зарегистрирован: 26.05.2017

Ссылка на код аддона https://github.com/rogerclarkmelbourne/Arduino_STM32

плата - Generic STM32F103C series, контроллер stm32f103c8

Если включить при компиляции LTO, то сыпется куча предупреждений, все однотипные, такого плана:

P:\Arduino\Sketches\hardware\Arduino_STM32\STM32F1\system/libmaple/include/libmaple/timer.h:150:18: warning: type of 'timer4' does not match original declaration [enabled by default]
 
extern timer_dev timer4;

                  ^
P:\Arduino\Sketches\hardware\Arduino_STM32\STM32F1\cores\maple\libmaple\timer.c:67:11: note: previously declared here

 timer_dev timer4 = GENERAL_TIMER(4);

Структура timer_dev описана в файле STM32F1/system/libmaple/include/libmaple/timer.h следующим образом

/** Timer device type */
typedef struct timer_dev {
    timer_reg_map regs;         /**< Register map */
    rcc_clk_id clk_id;          /**< RCC clock information */
    timer_type type;            /**< Timer's type */
    voidFuncPtr handlers[];    
} timer_dev;

а инициализируется в STM32F1/system/libmaple/timer_private.h как

timer_dev timer4 = GENERAL_TIMER(4);

где макрос GENERAL_TIMER() описан так:

#define NR_GEN_HANDLERS                 7

/* For declaring full-featured general purpose timers. */
#define GENERAL_TIMER(num)                                              \
    {                                                                   \
        .regs = { .gen = TIMER##num##_BASE },                           \
        .clk_id = RCC_TIMER##num,                                       \
        .type = TIMER_GENERAL,                                          \
        .handlers = { [NR_GEN_HANDLERS - 1] = 0 },                      \
}

Не очень силен в теории, приходится догоняться поиском в гугле. Насколько я понял, предупреждение появляется из-за использования в структуре так называемого flexible массива -

voidFuncPtr handlers[];

который в структуре обьявлен без указания длины, а окончательно описывается одновременнно с инициализацией уже в конкретных экхемплярах.

Два дня поиска в гугле так и не дали ответа, как убрать это предупреждение. Нашел несколько тем на стек-оверфлоу, но либо без ответов, либо с бесполезным выводом в конце, что это мол баг GCC и нифига не поделать. Но прошло уже года три с того ответа, а в самом последнем arm-none-eabi-gcc 11.2.1 этот глюк присутсвует. В дополнение вычитал вроде как МИСРА вообще запрещает использовать flexible массивы в структурах.

Собственно вопрос - как переписать код, чтобы предупреждение исчезло? мне пока ничего лучшего в голову не приходит, кроме как заменить flexible массив обычным с фиксированной длиной, выбрав длину с запасом. Но насколько это правильное решение? Может есть более элегантный метод?

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ничего не понял.

Если в одном месте у Вас написано

b707 пишет:

extern timer_dev timer4;

а в другом

b707 пишет:

timer_dev timer4 = GENERAL_TIMER(4);

То, почему Вас удивляет сообщение: "type of 'timer4' does not match original declaration"?

b707
Offline
Зарегистрирован: 26.05.2017

ЕвгенийП пишет:

почему Вас удивляет сообщение: "type of 'timer4' does not match original declaration"?

как я себе это понимаю, тип timer4 один и тот же - timer_dev. А  = GENERAL_TIMER(4); - это уже инициализация.

Разве мы не можем в одном юните написать

extern int x;

а в другом

int x =6;

разве это будут переменные разных типов?

 

Добавлю, что без опции -flto предупреждения нет.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

b707 пишет:

как я себе это понимаю, тип timer4 один и тот же - timer_dev. А  = GENERAL_TIMER(4); - это уже инициализация.

Ну, и так, и не так.

timer_dev - это т.н. недоопределённый тип ("incomplete type"), т.к. он содержит член - массив неопределённой длины, который сам по себе существовать не может (попробуйте просто объявить "int a[];" вне структуры) и типа как такового не имеет.

Чтобы убедиться, что этот член не имеет типа, посмотрите на этот скетч:

#define printVar(x) do { Serial.print(#x);  Serial.print('='); Serial.println(x); } while (false)

typedef struct Kaka {
    int a;
    int b[];
} Kaka;

Kaka k1;
Kaka k2 = { .a=10, .b={1,2,3,4,5,6} };

void setup(void) {
	Serial.begin(115200);
	printVar(k2.b[0]);
	printVar(k2.b[1]);
	printVar(k2.b[2]);
	printVar(k2.b[3]);
	printVar(k2.b[4]);
	printVar(k2.b[5]);
	printVar(sizeof(k1));
	printVar(sizeof(k2));
//	printVar(sizeof(k1.b));
//	printVar(sizeof(k2.b));
}

void loop(void) {
}

Всё классно работает. Результат:

k2.b[0]=1
k2.b[1]=2
k2.b[2]=3
k2.b[3]=4
k2.b[4]=5
k2.b[5]=6
sizeof(k1)=2
sizeof(k2)=2

Заметьте, что sizeof обоих массивов - 2, т.е. член b никак не влияет на размер структуры в обоих случаях. Хотя, в проинициализированном варианте этот массив получил правильные значения.

А теперь попробуйте раскомментировать строки 21 и 22. Увидите страшную ругань: "error: invalid application of 'sizeof' to incomplete type 'int []'".

т.е. тип члена b - недоопределён, а значит, недоопределён и тип всей структуры.

b707 пишет:

Добавлю, что без опции -flto предупреждения нет.

LTO - штука, во многом, эвристическая и ей очень важно точно знать, что типы действительно абсолютно одинаковые и никаких фокусов там будет. Потому, недоопределённые типы для LTO всегда различны. Здесь важно слово "всегда". Попробуйте, например, оба свои массива проинициализировать одинаково. Почти уверен, что LTO всё равно ругнётся (т.к. типы по-прежнему недоопределены)! Если не ругнётся, значит в очередной версии LTO они таки разрешили этот частный случай. Раньше ругалось, зуб даю.

Если бы это было не так, то перед LTO вставали бы уж слишком интеллектуальные задачи. Например, если бы он посчитал, что Ваши timer4 одинаковые и, обнаружив, что они не используются, решил бы их удалить. Что ему удалять? По sizeof? Или с учётом массива, который у одной переменной есть, а у другой - нет? 

b707
Offline
Зарегистрирован: 26.05.2017

Евгений, если я понял правильно - то выходит я "угадал" и причина предупреждения компилятора именно в "неполном массиве"

voidFuncPtr handlers[];

Точнее в том, что первоначально структра декларируется с неполным массивом, а инициализируется уже полным, поэтому типы получаются разными, правильно?

Проверил, замена на массив фиксированного размера полностью убирает все предупреждения.

Спасибо за обьяснения.

rkit
Offline
Зарегистрирован: 23.11.2016
/** Timer device type */
typedef struct timer_dev {
    timer_reg_map regs;         /**< Register map */
    rcc_clk_id clk_id;          /**< RCC clock information */
    timer_type type;            /**< Timer's type */
    voidFuncPtr handlers[NR_GEN_HANDLERS];    
} timer_dev;

 

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

имя типа и имя переменной - одинаковы(timer_dev), по идее это не варнинг а ошибка.

 

/** Timer device type */
typedef struct timer_dev {
    timer_reg_map regs;         /**< Register map */
    rcc_clk_id clk_id;          /**< RCC clock information */
    timer_type type;            /**< Timer's type */
    voidFuncPtr handlers[];    
} timer_dev;

P.S.  Нет, это я самдурак. 

rkit
Offline
Зарегистрирован: 23.11.2016

Это не имя переменной, это имя синонима типа, потому что typedef.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Вполне может быть, в Си не силён. Нахрена их 2 одинаковых описания :))) 

rkit
Offline
Зарегистрирован: 23.11.2016

Потмоу что иначе в C нужно писать каждый раз struct

struct timer_dev timer4 = GENERAL_TIMER(4);