Многие недопрограммисты, привыкшие, что тонкие места в их программах отслеживает и запрещает компилятор, не любят С за то, что в нем есть магическая штучка — 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();
}
Как насчет того, что sizeof(signed short) это уже грех? Если вы себя позиционируете как великие знатоки тонкостей С, то почему не написали sizeof(buff[0]) — это более безопасно (если однажды захотите изменить тип массива, не поймаете SIGSEGV).
Вообще, второй код, конечно больше импонирует.
@Тимур:
Мы не знатоки тонкостей, мы просто великие)
А вообще мы не будем никода ставить АЦП другого типа с этими контроллерами и слова там будут исключительно 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* и таким образом получать не просто код ошибки, а даже текстовое сообщение.
а разве sizeof выисляется не на этапе компиляции?
Да. Уже указали на это.
Спасибо, интересная фишка с этими jmp, лежит в clib, но ошибки всегда определенного типа, типа там деление на ноль, переполнение сетки и прочие.
Законектить свой обработчик после обработчика процессора очень легко, а это jmp все равно надо будет понять, что конкретно за ошибка случилась, процеесор же уже вызывает четкое exception, и даже если вся ОС повисла у нас еще есть время и возможность скинуть в какое нибудь NVRAM, почему система навернулась, какой адрес кода вызвал exception в том числе и имя task.
Если икзепшан некритеский можно управлять программой дальше зная о нем.
Кстати, использовать 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.
Да и вообще подобные вещи
(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
Вы невнимательно читали объявления переменных.
Я предполагал, что buff — это массив signed short, adc_buff — это массив unsigned long, как в первом примере. А вы что подумали?
Можно и покороче, но не очень наглядно, по-моему. А у кого больше варнингов при компиляции Си-кода забавное соревнование (пофигу на варнинги нам).
Реально гавнокод.
Цитата
«Многие недопрограммисты, привыкшие, что тонкие места в их программах отслеживает и запрещает компилятор, не любят С за то, что в нем есть магическая штучка — void *, с помощью которой возможны любые манипуляции с памятью. »
Ну выгоните двух студентов — найдите одного нормального инженера.
Здесь указатель на void вообще не нужен. memcpy тоже не нужет.
Еслу уж совсем забить на кроссплатофрменность и выравнивание, то хотя бы так написали
for (j = 0; j flags.adc_read_pass] = buff[j];
for (j = 0; j flags.adc_read_pass] = buff[j];
Блин, еще и комменты глючат.
for (j = 0; j flags.adc_read_pass] =
buff[j];
}
Напишите, что хотели сказать нормально, ваш гамнакод вообще не компилируется, даже мозгом, уважаемый Buver.
Студентов нету у нас, все нормальные инженеры.
Может вы хотите к нам инженером? Как раз две вакансии открыто, присылайте резюме.
Да, извините, чего-то не выспался. Просто не понравилась фраза про недопрограммистов и все такое.
Код чего-то не вставляется , попробую еще разок.
for (j = 0; j flags.adc_read_pass]
= buff[j];
Коменты не глючат, а вырезают спец-символы.
Хотите постить код, пользуйтесь тегом code :
for(j=0;j<1;j++)
эта строчка не стоит, чтобы ее писали три раза. Ну раз уж начал .
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++
Статья была про void *, ваш код рабочий, а наш еще и наглядный, а можно вообще вставочку на асме сделать))
Да в том то и дело, что void* вообще здесь не нужен. Указатель на void в основном используется как аргумент функции, которая может работать с различными типами данных ну или в других случаях когда нужно хранить что-то неизвестного типа. Да и memcpy вызывать чтобы скопировать 2 байта — тоже неверно. По поводу наглядности — этот ваш код с void* сложно читаемый, именно из-за непонятного использования void*, двух счетчиков цикла(зачем ??), и копирования двух байт с помощью memcpy.
Ну в конце же дан код, как правильнее, у нас вообще без счетчиков, т.е. еще круче)) Спасибо, за такое внимание к нашему коду.