КБ 13 и АЦП. Про гибкость C

Многие недопрограммисты, привыкшие, что тонкие места в их программах отслеживает и запрещает компилятор, не любят С за то, что в нем есть магическая штучка — void *, с помощью которой возможны любые манипуляции с памятью. Например, он позволяет подобные фокусы:

int i=0,j=0;
signed short buff[16];
unsigned long adc_buff[16];

for(i=0,j=0;j<16;i+=2,j++){
	memcpy( (void *)adc_buff + (i + bus_p->flags.adc_read_pass)*sizeof(signed short),
			(void *)buff + j*sizeof(signed short),
			sizeof(signed short)
			);
}

Но читать (не то что контролировать) свой код при этом становится довольно сложно. И наловить, например, SIGSEGV-ов чисто от ненимательности — ничего не стоит. Впрочем, необходимость быть особенно внимательным при написании кода — небольшая плата за возможности обращаться с памятью как хочешь.

А суть этого кода такова.
Наибольшая скорость записи в SDRAM в нашем АЦП достигается при прожиге 32 бит — т.е. лонгового значения.
АЦП с каждого канала возвращает short-значение — 16 бит.

Таким образом, становится ясно, что скидывать буфера в память эффективнее всего каждые два чтения:

  • будет достигнута максимальная скорость записи;
  • память будет использована по максимуму;
  • буфера не будут перемешаны.

В вышеприведенном куске кода как раз и делается необходимая для этого манипуляция — за два считывания собирается буфер long-ов для последующей записи в sdram.
Неизвестная читателю переменная flags.adc_read_pass хранит номер чтения — первый или второй. Точнее, нулевой или первый — и в зависимости от этого происходит сдвиг адреса, куда мы записываем значения, или нет.

Но этот код, конечно, не оптимален — он занимает время на выполнение функции memcpy. Проще и эффективнее воспользоваться все той же гибкостью языка в приведении типов и сделать немного по-другому. Примерно вот так:

void ain16_get_adc_values(signed short * adc_buffer)
{
	adc_buffer[0]  = (*(EXT_ADC_ADDR))<<2;		// Read channel 0
	adc_buffer[2]  = (*(EXT_ADC_ADDR))<<2;
	adc_buffer[4]  = (*(EXT_ADC_ADDR))<<2;
	adc_buffer[6]  = (*(EXT_ADC_ADDR))<<2;
	adc_buffer[8]  = (*(EXT_ADC_ADDR))<<2;
	adc_buffer[10] = (*(EXT_ADC_ADDR))<<2;

	adc_buffer[12] = (*(EXT_ADC_ADDR+1))<<2;
	adc_buffer[14] = (*(EXT_ADC_ADDR+1))<<2;
	adc_buffer[16] = (*(EXT_ADC_ADDR+1))<<2;
	adc_buffer[18] = (*(EXT_ADC_ADDR+1))<<2;
	adc_buffer[20] = (*(EXT_ADC_ADDR+1))<<2;
	adc_buffer[22] = (*(EXT_ADC_ADDR+1))<<2;

	adc_buffer[24] = (*(EXT_ADC_ADDR+2))<<2;
	adc_buffer[26] = (*(EXT_ADC_ADDR+2))<<2;
	adc_buffer[28] = (*(EXT_ADC_ADDR+2))<<2;
	adc_buffer[30] = (*(EXT_ADC_ADDR+2))<<2;	// Read channel 15
}

void
adc_read(ain16_mainbus_t * bus_p)
{
	static unsigned long adc_buff[16];

	ain16_adc_start();

	ain16_get_adc_values( (signed short *)( (void*)adc_buff + bus_p->flags.adc_read_pass*sizeof(signed short) ) );

	ain16_adc_stop();
}

Похожий бред:

КБ 13 и АЦП. Про гибкость C: 27 комментариев

  1. Как насчет того, что sizeof(signed short) это уже грех? Если вы себя позиционируете как великие знатоки тонкостей С, то почему не написали sizeof(buff[0]) — это более безопасно (если однажды захотите изменить тип массива, не поймаете SIGSEGV).

    Вообще, второй код, конечно больше импонирует.

  2. @Тимур:
    Мы не знатоки тонкостей, мы просто великие)
    А вообще мы не будем никода ставить АЦП другого типа с этими контроллерами и слова там будут исключительно 16 бит.
    sizeof (buff[0]) — некрасиво, в остальном вы правы.
    А красиво вот так:
    #typedef unsigned short ADC_TYPE_WORD ;
    ADC_TYPE_WORD buff[16];

    • я бы написал именно sizeof(buff[0]) по следующей причине:
      АЦП может и не изменится, измениться может например микроконтроллер, может измениться разработчик, занимающийся поддержкой, допустим (по недоразумению) кто-то решил, что ADC_TYPE_WORD — не красиво, т.к. громоздко и вообще не похоже на uint16_t. И изменил
      ADC_TYPE_WORD buff[16] на
      uint16_t buff[16];

      а в коде забыл изменить ADC_TYPE_WORD на uint16_t.
      и typedef (кстати, без #) не убрал.

      А допустим процессор стал по какой-то причине не 32, а 16 либо 64 разрядным (очень-очень маловероятно, но у великих должно быть предусмотрено всё 🙂 ). Размер short int-a нигде не регламентируется, хотя почти везде он и 16 бит. А тут окажется 8 или 32. И всё… приехали.

      Эта запись, sizeof(buff[0]) — она в смысле алгебры Кодда более «нормальна» чем sizeof(unsigned short int) или sizeof(ADC_TYPE_WORD) потому что исключает дублирование данных. Т.е.
      соотношения такие :
      в вашем случае (даже украшенном)
      unsigned int <—ADC_TYPE_WORD<—buff[0]
      unsigned int <—ADC_TYPE_WORD<—sizeof(ADC_TYPE_WORD)

      т.е. на уровне ADC_TYPE_WORD вилка, которая вынудит вас лазить по всему коду, если изменится соотношение между buff[0] и ADC_TYPE_WORD.

      в моём случае
      unsigned int <— buff <— buff[0]

      как бы не менялся тип массива buff — лазить по коду не придется.

      Хотя, надо сказать, что это, наверно, один из религиозных вопросов, спорить на тему которых — лишь тратить время зря 🙂

      P.S. Я про эту особенность узнал чисто случайно… на позапрошлой работе к нам пришли два программиста (более опытных, чем я был тогда) и я услышал от них фразу : "мы сторонники безопасного программирования, поэтому никогда не используем sizeof". Я тогда очень удивился… подумал, а чем вообще может быть опасен sizeof… Долго это для меня было загадкой, пока не вычитал в одной книжке ("Искусство программирования на С") почему же это опасно. С тех пор начал sizeof использовать очень аккуратно, хотя и не отказался совсем уж… потому что не понятно, как на С без него… на ++ может и можно, а на С никак (мне так кажется).

      • Вопрос, конечно, религии. Просто парни, которые продают нам ОСы за сотки тонн баксов, пишут применительному к системному ПО именно так, как я написал — красиво. Мы тоже хотим много мульенов, а так вообще, спаисбо за камент в апликейэшан приложениях будем учитывать.
        На всякие фигни типа забытого # внимания обращать не стоит, нас могут читать студенты, и мы не имеем морального права дать им скопипастеть кусочек за просто так))

      • Как автор этого дебатного 🙂 кода скажу следующее.

        Сайзоф там используется чисто для мнемоники — чтобы не было мэджик намберс.

        Реально же туда надо подставлять что-то вроде такого:

        #define SIZEOF_ADC_WORD 2
        ain16_get_adc_values( (signed short *)( (void*)adc_buff + x * SIZEOF_ADC_WORD ) );

        По той простой причине, что мы гоняем не на П4 2ГГц, а временные промежутки у нас в микросекундах — тратить такты проца на вычисление сайзофа каждый раз таки расточительно.

        • Хотел промолчать, но не могу… ну как написали, так написали… да и ладно… работает и хорошо… очень многие пишут совсем никак и это работает, и «пипл хавает».

          Но sizeof не будет вычисляться каждый раз — это же оператор… если компилятор может вычислить значение выражения на этапе компиляции — он это делает… не верите — посмотрите на ассемблерный листинг. Если написать что-то вроде
          printf(«5+6=%d»,5+6);

          то в листинге вы увидите как на стек кладется указатель на строку формата, потом 11 (либо наоборот 11, а потом указатель… не помню щас, а проверять лень) и вызывается printf. Т.е. считать он не будет каждый раз… и такое поведение (может я и не прав, конечно, но думаю) — оно даже при полностью отключенной оптимизации. Доказательство здесь: http://en.wikipedia.org/wiki/Sizeof — написано compile time operator.

          Добавлю, что это не я умный тут такой, просто тоже случай из жизни… когда на эту позапрошлую работу устраивался, сходил еще в несколько контор на собеседования — везде прошел, а в одной — нет. Этой конторой была LG. Высушили они меня за плохое (тогда) знание английского и за то, что думал, что sizeof вычисляется в runtime.
          Вообще, я заметил, что язык Си очень коварен… Программируешь на нём черти знает сколько, думаешь, что всё в нем знаешь, ан нет, чего-то да всплывает… В качестве затравки скажу (хоть и предвижу возможный шквал наездов) — знает ли кто-нибудь, что в Си есть очень близкий аналог исключений Си++? Почти на 100% уверен, что никто не знает, а может быть и будете говорить, что нету такого механизма (goto не в счет, т.к. стек не откручивает назад, да и из одной функции в другую по goto не прыгнуть). Уверен, не потому что вас хочу как-то обидеть, а потому что не видел еще ни одного человека, который бы знал (не считая (это уже подсказка) двух парней, одного из которых уже нет в живых — одного зовут Ричард Стивенс, а другого Стивен Раго), при том, что это достаточно стандартная возможность (сам очень удивился, когда узнал).

          Сохраню интригу… но если хотите — в следующем посте скажу, о чем речь.
          Извините, что я тут влез вообще… мне очень неудобно, честное слово… критиковать больше не буду и умника строить тоже 🙂
          Удачи во всех разработках!

          • Это у нас молодой программер), старые конечно знают, что сайзоф уже высчитаным идет в функцию, да и конвеер у этого мегакантролера такой, что даже и не выщитанный не нанес бы урона количесвту тактов в сторону увеличения.

            А пра исключения расскажите, а то мы только getLastError() знаем и тупо подвеситься на все икзепшаны процессора своими хэндлерами можем. Могли бы погуглить, но чего не юзаем, того не знаем)

          • Про исключения в Си: это две функции, setjmp и longjmp.

            Исключения процессора чтоб обрабатывать, всё равно придется мучаться с сигналами и т.д. т.е. это не такой механизм, как try… catch который автоматом всё ловит.

            Но с помощью этого механизма (setjmp/longjmp) можно обрабатывать исключения, которые вы будете поднимать сами. Это тоже не так уж плохо. Т.е. можно не делать чтоб ф-я возвращала код ошибки, а делать так, что она будет генерировать исключительную ситуацию и управление будет передаваться в то место кода, где идет обработка.

            http://ru.wikipedia.org/wiki/Longjmp

            более подробно смотрите в книжке Стивенса и Раго «UNIX. Профессиональное программирование». Её можно купить на books.ru (она такая в дерзко розовом переплёте) либо там же — в электронном виде (в т.ч. через оплату по СМС с билайна — стоит 350 рублей). Книжка очень стоящая, хоть и стоит дорого — около 1000 рублей за бумажный вариант. Лучше иметь оба варианта и электронный и бумажный, т.к. они друг дружку дополняют.

            Возвращаясь к setjmp/longjmp — описаны они на странице 252. Пример использования — на стр. 255.

          • да, механизм этот не Си-шный, а предусмотренный POSIX, судя по Википедии (я думал, что он входит в стандартную библиотеку). И судя же по Википедии есть функции аналогичные sigsetjmp и siglongjmp для исключительных ситуаций, возникающих внутри операционной системы.

            Т.к. прокололся, то приведу пример из книжки, как этот механизм используется…

            int buffer;

            int main(int argc, char *argv[])
            {
            …………….
            //какой-то код
            …………….
            if (setjmp(buffer) != 0)
            {
            //обработка исключительной ситуации, в buffer
            //код ошибки
            …………..
            }
            ………………….
            my_function();
            ………………….
            }

            void my_function(void)
            {
            ………………..
            if (bad_condition)
            {
            int error_code = 10;
            longjmp(buffer, error_code);
            }
            }

            Использовать этот механизм на практике — честно скажу — не использовал… наверно опять же дело привычки. Но есть идея, что можно в качестве error_code приводить указатель на строку к int-у, а потом его обратно приводить к char* и таким образом получать не просто код ошибки, а даже текстовое сообщение.

  3. Спасибо, интересная фишка с этими jmp, лежит в clib, но ошибки всегда определенного типа, типа там деление на ноль, переполнение сетки и прочие.
    Законектить свой обработчик после обработчика процессора очень легко, а это jmp все равно надо будет понять, что конкретно за ошибка случилась, процеесор же уже вызывает четкое exception, и даже если вся ОС повисла у нас еще есть время и возможность скинуть в какое нибудь NVRAM, почему система навернулась, какой адрес кода вызвал exception в том числе и имя task.
    Если икзепшан некритеский можно управлять программой дальше зная о нем.

  4. Кстати, использовать void* в арифметических выражениях не рекомендуется. Лучше заменить на char *. GCC даже ворнинг может выдавать по этому поводу:

    -Wpointer-arith
    Warn about anything that depends on the «size of» a function type or of void. GNU C assigns these types a size of 1, for convenience in calculations with void * pointers and pointers to functions. In , warn also when an arithmetic operation involves NULL. This warning is also enabled by -pedantic.

  5. Да и вообще подобные вещи
    (void *)buff + j*sizeof(signed short)
    (signed short *)( (void*)adc_buff + bus_p->flags.adc_read_pass*sizeof(signed short)
    пишутся несколько короче:
    buff + j
    (signed short *)adc_buff + bus_p->flags.adc_read_pass

  6. Можно и покороче, но не очень наглядно, по-моему. А у кого больше варнингов при компиляции Си-кода забавное соревнование (пофигу на варнинги нам).

  7. Реально гавнокод.
    Цитата
    «Многие недопрограммисты, привыкшие, что тонкие места в их программах отслеживает и запрещает компилятор, не любят С за то, что в нем есть магическая штучка — void *, с помощью которой возможны любые манипуляции с памятью. »
    Ну выгоните двух студентов — найдите одного нормального инженера.
    Здесь указатель на void вообще не нужен. memcpy тоже не нужет.
    Еслу уж совсем забить на кроссплатофрменность и выравнивание, то хотя бы так написали
    for (j = 0; j flags.adc_read_pass] = buff[j];

  8. Напишите, что хотели сказать нормально, ваш гамнакод вообще не компилируется, даже мозгом, уважаемый Buver.
    Студентов нету у нас, все нормальные инженеры.
    Может вы хотите к нам инженером? Как раз две вакансии открыто, присылайте резюме.

  9. Да, извините, чего-то не выспался. Просто не понравилась фраза про недопрограммистов и все такое.
    Код чего-то не вставляется , попробую еще разок.

    for (j = 0; j flags.adc_read_pass]
    = buff[j];

  10. эта строчка не стоит, чтобы ее писали три раза. Ну раз уж начал .

    for (i = 0; i < 16; i++)
    ((short*)adc_buff)[i * 2 + adc_read_pass] = buff[j]

    Ну или еще проще

    for ( i = adc_read_pass; i < 32; i+=2)
    ((short*)adc_buff)[i] = *buff++

  11. Да в том то и дело, что void* вообще здесь не нужен. Указатель на void в основном используется как аргумент функции, которая может работать с различными типами данных ну или в других случаях когда нужно хранить что-то неизвестного типа. Да и memcpy вызывать чтобы скопировать 2 байта — тоже неверно. По поводу наглядности — этот ваш код с void* сложно читаемый, именно из-за непонятного использования void*, двух счетчиков цикла(зачем ??), и копирования двух байт с помощью memcpy.

  12. Ну в конце же дан код, как правильнее, у нас вообще без счетчиков, т.е. еще круче)) Спасибо, за такое внимание к нашему коду.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Protected by WP Anti Spam