воскресенье, 2 ноября 2008 г.

Немного о программировании

Тут мне задали один интересный вопросик.

Предистория: есть некоторая конструкция типа такой

typedef struct {
BYTE Id; // Идентификатор начала пакета
BYTE Cop; // Тип пакета
WORD Len; // Длинна информационной части (поля Data)
BYTE Data[]; //Данные, упакованные в пакет
} rcvPkt;

Конструкция сия прекрасно обрабатывается всеми попавшимися мне компиляторами С для микроконтроллеров AVR.

Одна загвоздка. Поле Data мало того что переменной длинны, оно еще и содержит собственную структуру данных. Да, конечно самым правильным вариантом было бы написать что-то типа

typedef struct {
WORD id;
WORD offset;
BYTE data[];
} memInfo;

И в программе сделать сделать что-нить типа

rcvPkt *pkt;
memInfo *tmp;

tmp = (memInfo*)pkt->Data;

и обращаться уже к "нормальным" и хорошо читаемым полям. Однако, в силу ряда причин связанных с ограниченным размером стека для локальных переменных, нежеланием плодить гору глобальных переменных и довольно простой структурой данных и информационной части пакета (в реальной жизни там всегда смещение, но в зависимомти от поля Cop оно может быть либо BYTE, либо WORD, либо вообще отсутствовать ) делать этого не хочется.

Теперь к сути вопроса. Я использовал конструкцию типа
(WORD)(*(WORD*)&Data[2])
для получения значения поля offset. И был задан вопрос почему бы не воспользоваться более простым вариантом
(WORD)*(Data+2)

Отвечаю. Увы, но специфика задачи требует, что бы код компилировался двумя разными компиляторами (AVR-GCC и IAR Systems ICC for AVR). А вот создатели компиляторов вторую строку видят по разному. В исходном варианте был код примерно такой, какой мне рекомендуют, и именно поэтому могу смело заявить, что для кроскомпиляторной обработки лучше первый вариант.

Итак, разбираем конструкцию (WORD)(*(WORD*)&Data[2]) с позиции компилятора. Сразу замечу, что для любого уважающего себя компилятора записи &Data[2] и Data+2 абсолютно идентичны. И в первом и во втором случае мы получим указатель на тип BYTE со смещение 2 от базы, коей в нашем случае является Data.

А дальше начинается очень интересный момент. К сожалению, прямо сейчас я не смогу указать точной привязки к компилятору, но суть в том, что дальнейшая их обработка кода здорово отличается. Итак, Начинаем с моей записи. Приведение типа к (WORD*) - указатель сам по себе абстрактен. Однако у нас был не просто указатель, а указатель на тип BYTE. Теперь он стал указателем на тип WORD. И это съедают оба компилятора, благо это ничему не противоречит. Далее идет звездочка - т.е. взятие значения по указателю. Так как наш указатель указывает на тип WORD, то любой компилятор возьмет значение именно типа WORD (т.е. 2 байта). На самом деле тут уже можно бы и успокоиться. По совести говоря первое (по записи, конечно, а не по последовательности выполнения) приведение типа к WORD не является необходимым, но по совершенно непонятным мне причинам позволяет избавиться от "лишних" двух байт генеририруемых одним из компиляторов. Так как на работу другого эта запись никак не влияет, решено было ее оставить.

Думаю, уже понятно чем плоха вторая запись. Да, одни из компиляторов ее съел и она работала. Зато другой... Итак:
(WORD)*(Data+2)
Ну, про Data+2 я уже писал - BYTE*. Далее идет взятие значения. А вот он и камень. Так как указатель на BYTE, то и берется BYTE. И уже взятый BYTE расширяется до слова. Так как в архитектуре AVR первой идет младшая часть, мы напрочь теряем сташую. Именно от этого неприятного момента я и избавлялся нудно объясняя компилятору где какие типы лежат и что с ними делать.

Второй компилятор, видимо считал что любой указатель по сути является указателем на тип void и выделением типа занимался в последний момент. Я не возьмусь утвеждать что будет более правильно (тем более, что самый правильный вариант описан в начале поста). Однако, слава богу поиск данной заморочки не затянулся (понадобилось ровно три минуты для того чтобы поправить код в четырех местах).

Вывод очевиден. Не надо надеяться на "умный компилятор". Черевато крупными неприятностями... А уж про указатели в С вообще писано-переписано...