Напоролся на забавные грабли

Полдня искал глюк в отправке данных с STM32F100 по uart на комп. Решил поделиться, вдруг кому поможет, ну или чисто поржать (надо мной). Осторожно, внутри скукота и ни одной картинки.

Отлаживаю прошивку на очередной своей поделке. Шестипортовая USB-зарядка для разросшегося парка устройств. STM32 в ней для измерения токов, что-то вроде встроенного charge doctor’a. Ну там ещё пара фишек вроде 12В каналов для питания зарядок лития и никеля, защиты, подсчёта калорий амперчасов. Дабы весь этот зоопарк занимал всего одну розетку. Тем более что мне удачно нахаляву достался meanwell'овский БП с двумя выходами, 5 и 12 вольт. Ну да суть не в этом. Главное что есть stm32f100C4 и usart порт для отладки, а в будущем, для rs-485 (нафига зарядке rs-485? хз, пусть будет, любой девайс становится лучше с блютузом rs-485). Прошивку уже ковыряю пару дней, заодно плату проверяю, всё равно её буду переделывать, вылез один критичный косяк в задумке. Прошивка большей частью из кусков кода, который уже многократно использовался, чего там, ADC, экранчик от nokia 1202, uart да пара таймеров. Казалось бы, ну чего там делать то? А тут второй день страшные грабли. Вчера вообще непонятное было. Запустил подсветку экрана через аппаратный шим с TIM1. Потупил немного, но запустил. Работает. Делаю резет питанием — подсветка мигает с частотой раза 3 в секунду. Когда горит — ШИМ есть, яркость регулируется. Но мигает. Делаю сброс через прошивку (st-link и SWD) — работает! Чем отличается в данном случае резет программатором от резета по питанию — так и не понял. Потыркался, переписал инициализацию таймера через регистры, глюк ушёл. Обиделся, наверное. И хорошо бы всё свалить на глючность StdPeriphLib, но боюсь дело не в ней было.

Но я опять отвлекся. Сегодняшние грабли. В процессе отладки кидаю в порт циферки, на компе смотрю. И тут заметил, что в данных иногда явно меняется одна циферка. Начал изучать. Думал может с данными с АЦП что-то. Стал тупо отправлять константу, получаю столбцом это число, но изредка третья цифра в этом числе меняется на единицу. Думал, может глючит код преобразования числа в строку — нет. Заменил просто на код отправки 6 символов и перевода строки — глючит. Попробовал поменять скорость с 115200 бод на поменьше — глюк вроде пропал. Помехи? Частота уплывает (кварца нет, тактирование от HSI)? Ну беру логический анализатор, ds203, смотрю ими. Сигнал чёткий, криминала нет. Логический анализатор показывает, что глюк есть и это не помехи, значит stm'ка так передаёт. Глючит где-то в коде, буфер затирается-переполняется? Убираю из кода всё лишнее — не помогает. Может чип глючит? Заливаю в другой — нифига. Дальше пошли долгие эксперименты с кодом модуля usart. Менял размер буфера, отправлял константы, много думал. В итоге понял, где бага порылась.

Мой модуль usart написан на основе кода от CodeVision AVR, перекочевал из старых проектов. Ну собственно код организации циклических буферов приёма-передачи, там всего-то десяток строчек, всё проверено многократно. Суть там в том что в очередь на передачу есть два указателя. Один для записи, его меняет только putchar(). Символ положил, указатель сдвинул. Второй — указатель чтения, его меняет только прерывание по готовности отправить следующий байт. Забрали байт, сдвинули указатель. Таким образом всё надежно работает без блокирования прерываний и прочего. Ага, я так думал. Вот казалось бы, какая проблема может быть в строках:
usart1_tx_buf[tx_buf_wr1++]=c;
if (tx_buf_wr1 >= TX_BUF_SIZE1) tx_buf_wr1=0;

Положили символ в буфер, увеличили указатель записи. Потом зациклили его, если вылез за границу. Даже если между операциями произойдёт прерывание, ничего страшного не будет. Обработчик заберёт свежеположенный байт на отправку и всё. Дело не в этом. От того, отмечена ли переменная tx_buf_wr1 модификатором volatile тоже не зависит. С зацикливанием проблем нет. С переполнением тоже.

Ну что, угадали, где тут глюк?

А глюк в первой строке. Keil её скомпилировал вот так:

232:     usart1_tx_buf[tx_buf_wr1++]=c; 
0x08001142 4921      LDR      r1,[pc,#132]  ; @0x080011C8
0x08001144 6808      LDR      r0,[r1,#0x00]
0x08001146 1C41      ADDS     r1,r0,#1
0x08001148 4A1F      LDR      r2,[pc,#124]  ; @0x080011C8
0x0800114A 6011      STR      r1,[r2,#0x00]  ; <-- тут сохраняем инрементированный указатель
0x0800114C 4925      LDR      r1,[pc,#148]  ; @0x080011E4
0x0800114E 540C      STRB     r4,[r1,r0]  ; <-- а только тут сохраняем сам байт в очередь

(Оптимизация кода отключена)
Т.е. получается что-то вроде:
берём tx_buf_wr1 в r0
прибавляем единицу (r0 при этом не изменяем)
сохраняем в tx_buf_wr1
записываем байт в usart1_tx_buf + r0

Таким образом указатель инкрементируется на две команды раньше, чем байт записывается в буфер. И как должно было повезти, чтобы именно в этот момент прерывание usart_txe лезло за следующим байтом? Не знаю сколько там тактов на эти операции, но шансы так попасть — один на миллион, я думаю.

А что, если бы оптимизация была включена? Тут тоже интересно. Если переменная tx_buf_wr1 обычная, то глюк проходит. Оптимизатор её не записывает в память, пока не выполнит следующую строку, проверку на зацикливание. А вот если она volatile, то получается следующий код:
0x08000DBC 6111      STR      r1,[r2,#0x10] ; указатель
0x08000DBE 7018      STRB     r0,[r3,#0x00] ; символ в буфер

Как видно, шансы попасть снижаются вдвое, но всё равно остаются.

Короче заменил на следующий код:
usart1_tx_buf[tx_buf_wr1]=c;
tx_buf_wr1=(tx_buf_wr1+1)%TX_BUF_SIZE1;

Думаю такой вариант вполне надёжен.

С этим разобрался, посмотрим, какие грабли мне готовит день грядущий.

В комментах жду замечаний на тему «это каждому известно, на порядок выполнения постинкремента в С полагаться нельзя» и «ну ты и лох!». Хотя посмотрел, вот к примеру тут потенциально такой-же баг.
  • +2
  • 11 марта 2015, 01:02
  • ACE

Комментарии (69)

RSS свернуть / развернуть
У меня немного по другому:
tx_head = (usart_fifo.tx_buffer_head + 1) % USART_TX_BUFFER_SIZE;

while (tx_head == usart_fifo.tx_buffer_tail)
{
}

usart_fifo.tx_buffer[tx_head] = data;
usart_fifo.tx_buffer_head = tx_head;
0
  • avatar
  • ZiB
  • 11 марта 2015, 06:54
tx_head = (usart_fifo.tx_buffer_head + 1) % USART_TX_BUFFER_SIZE;


Кстати вот этот значок "%" много за собой тянет. совершенно не оправдано его использование в данном случае.
уж лучше так:

if ((usart_fifo.tx_buffer_head + 1) == USART_TX_BUFFER_SIZE)
  tx_head = 0;
else
  tx_head = usart_fifo.tx_buffer_head + 1;
0
Лучше вместо "==" использовать ">=".
Но вернемся к моему коду, мне не нравиться условие :)
Да и в случае, если буфер будет равен степени два N^2, то это будет всего лишь одна инструкция И, в других случаях две или три зависит от размера.
0
инструкция типа «UDIV» будет скорее всего (если это ARM), которая занимает 42 такта (если не ошибаюсь) процессора. Ну а дальше вновь умножение, вычитание, и после — сравнение… Короче, тот еще пирог. А если это AVR — то тактов на 100 наберется…
0
да, я забыл указать, что использую stm32. для него это максимум 12 тактов на *div.
но пожалуй соглашусь, переписал вот так ;)
if ((rx_tail = (_fifo_u1.rx_buffer_tail + 1)) >= USART_RX_BUFFER_SIZE)
	rx_tail = 0;
0
Тоже потенциально опасно. rx_tail может (в зависимости от компилятора) на какое-то время стать равным USART_RX_BUFFER_SIZE и только потом зациклиться на 0. Мне лично вариант с % больше нравится, фиг со скоростью.
Может как-то так:
rx_tail = (_fifo_u1.rx_buffer_tail < USART_RX_BUFFER_SIZE)? _fifo_u1.rx_buffer_tail + 1: 0;
0
Упс,
rx_tail = (_fifo_u1.rx_buffer_tail+1 < USART_RX_BUFFER_SIZE)? _fifo_u1.rx_buffer_tail + 1: 0;
0
rx_tail = (_fifo_u1.rx_buffer_tail+1 < USART_RX_BUFFER_SIZE)? _fifo_u1.rx_buffer_tail + 1: 0;
лучше уж тогда
rx_tail = (_fifo_u1.rx_buffer_tail < USART_RX_BUFFER_SIZE — 1)? _fifo_u1.rx_buffer_tail + 1: 0;
0
Так делать нельзя!
В данном случае нужна «атомарность» доступа к глобальной переменной "_fifo_u1.rx_buffer_tail", а она не обеспечивается.
0
Если запись в очередь идёт из нескольких потоков — тогда да. А если из одного, а другие потоки/прерывания только читают из очереди — то вроде нормально. Запись инкрементированного значения вполне атомарна.
0
К сожалению комментария хрен поймешь как располагаются, во всяком случае мне тяжело точно сказать к какому сообщению ваше замечание.

Если речь идет о коде
rx_tail = (_fifo_u1.rx_buffer_tail < USART_RX_BUFFER_SIZE — 1)? _fifo_u1.rx_buffer_tail + 1: 0;

То я не согласен с вами.

Переменная "_fifo_u1.rx_buffer_tail" глобальная (используемая в двух потоках, см. ниже), и в данной инструкции вы читаете её значение, сравниваете и потом после производите ещё одно чтение. А это может привести к тому, что значение переменной между этими действиями будет изменено.

Касательно потоков. Мы сейчас рассматриваем приём (собственно с передачей тоже самое). Запись в буфер производится в потоке обработчика прерываний по приему. Чтение производится из другого потока. Т.е. запись один поток и чтение один поток.
Да же в этом контексте приведенный выше код уже не работоспособен, по причинам указанным выше.
А если есть желание делать запись или чтение из нескольких потоков, то нужно рассмотреть весь код, а не кусочки.
0
если один поток пишет и один поток читает — то этот код работоспособен. Что может измениться вдруг, если указатели на чтение и на запись разные в фифо буфере? Один поток меняет указатель на запись, другой на чтение.
0
Мне лично вариант с % больше нравится
Чтобы оценить масштаб «бедствия» специально сделал программу для AVR и скомпилил c максимальной оптимизацией. Предлагаю листинг:
a = (a+1) % 15;
 222:	2f e0       	ldi	r18, 0x0F	; 15
 224:	30 e0       	ldi	r19, 0x00	; 0
 226:	89 81       	ldd	r24, Y+1	; 0x01
 228:	90 e0       	ldi	r25, 0x00	; 0
 22a:	01 96       	adiw	r24, 0x01	; 1
 22c:	b9 01       	movw	r22, r18
 22e:	0e 94 21 01 	call	0x242	; 0x242 <__divmodhi4>
 232:	89 83       	std	Y+1, r24	; 0x01
		if((a = a+1)>=15) a = 0;
 234:	89 81       	ldd	r24, Y+1	; 0x01
 236:	8f 5f       	subi	r24, 0xFF	; 255
 238:	89 83       	std	Y+1, r24	; 0x01
 23a:	8f 30       	cpi	r24, 0x0F	; 15
 23c:	a0 f3       	brcs	.-24     	; 0x226 <main+0x12>
 23e:	19 82       	std	Y+1, r1	; 0x01
 240:	f2 cf       	rjmp	.-28     	; 0x226 <main+0x12>

00000242 <__divmodhi4>:
 242:	97 fb       	bst	r25, 7
 244:	07 2e       	mov	r0, r23
 246:	16 f4       	brtc	.+4      	; 0x24c <__divmodhi4+0xa>
 248:	00 94       	com	r0
 24a:	07 d0       	rcall	.+14     	; 0x25a <__divmodhi4_neg1>
 24c:	77 fd       	sbrc	r23, 7
 24e:	09 d0       	rcall	.+18     	; 0x262 <__divmodhi4_neg2>
 250:	0e 94 35 01 	call	0x26a	; 0x26a <__udivmodhi4>
 254:	07 fc       	sbrc	r0, 7
 256:	05 d0       	rcall	.+10     	; 0x262 <__divmodhi4_neg2>
 258:	3e f4       	brtc	.+14     	; 0x268 <__divmodhi4_exit>

0000025a <__divmodhi4_neg1>:
 25a:	90 95       	com	r25
 25c:	81 95       	neg	r24
 25e:	9f 4f       	sbci	r25, 0xFF	; 255
 260:	08 95       	ret

00000262 <__divmodhi4_neg2>:
 262:	70 95       	com	r23
 264:	61 95       	neg	r22
 266:	7f 4f       	sbci	r23, 0xFF	; 255

00000268 <__divmodhi4_exit>:
 268:	08 95       	ret

0000026a <__udivmodhi4>:
 26a:	aa 1b       	sub	r26, r26
 26c:	bb 1b       	sub	r27, r27
 26e:	51 e1       	ldi	r21, 0x11	; 17
 270:	07 c0       	rjmp	.+14     	; 0x280 <__udivmodhi4_ep>

00000272 <__udivmodhi4_loop>:
 272:	aa 1f       	adc	r26, r26
 274:	bb 1f       	adc	r27, r27
 276:	a6 17       	cp	r26, r22
 278:	b7 07       	cpc	r27, r23
 27a:	10 f0       	brcs	.+4      	; 0x280 <__udivmodhi4_ep>
 27c:	a6 1b       	sub	r26, r22
 27e:	b7 0b       	sbc	r27, r23

00000280 <__udivmodhi4_ep>:
 280:	88 1f       	adc	r24, r24
 282:	99 1f       	adc	r25, r25
 284:	5a 95       	dec	r21
 286:	a9 f7       	brne	.-22     	; 0x272 <__udivmodhi4_loop>
 288:	80 95       	com	r24
 28a:	90 95       	com	r25
 28c:	bc 01       	movw	r22, r24
 28e:	cd 01       	movw	r24, r26
 290:	08 95       	ret

Условие заняло всего шесть инструкций, а % — лень считать если честно…
0
Да, для AVR ощутимо. На STM32 с % на 4 байта короче вышло. Ну правда % так и так уже используется в других местах, соптимизировалось.
А код подтверждает потенциальную опасность именно такой записи. Счётчик кратковременно вылезает за границы очереди. Лучше тогда вначале сравнить, а потом присвоить результат.
0
Счётчик кратковременно вылезает за границы очереди
Да. Здесь это явно на лицо. На заметку ZiB'у))
0
Будьте внимательны это локальная переменная!!!
В данном случае нужна «атомарность» доступа к глобальной переменной и она обеспечивается.
Поправьте, если я не прав.
0
ну если локальная, тогда нет вопросов. Начальные условия были неизвестны))
0
да, вроде из кода и так понятно :) или можно было уточнить…

В целом конечно странно. Такие вещи очень часто встречаются в коде и непонимание этих процессов довольно странно ;)
0
Вот «первая» моя публикация под стм32
ziblog.ru/2011/04/18/stm32-ndash-usart-fifo.html

посмотрите первый коммент, видимо я как-то неправильно пишу код, что он плохо читаем. сказывается отсутствие образования по кодингу :)
0
максимальная производительность и корректность удалось добиться при следующем коде:
b = a + 1;
 234:	89 81       	ldd	r24, Y+1	; 0x01
 236:	8f 5f       	subi	r24, 0xFF	; 255
		(b >= 15)? a = 0 : a = b;
 238:	8f 30       	cpi	r24, 0x0F	; 15
 23a:	10 f0       	brcs	.+4      	; 0x240 <main+0x2c>
 23c:	19 82       	std	Y+1, r1	; 0x01
 23e:	f3 cf       	rjmp	.-26     	; 0x226 <main+0x12>
 240:	89 83       	std	Y+1, r24	; 0x01
0
и еще один неплохой вариант:
(a < 15 - 1)? a++ : a = 0;
 234:	89 81       	ldd	r24, Y+1	; 0x01
 236:	8e 30       	cpi	r24, 0x0E	; 14
 238:	20 f4       	brcc	.+8      	; 0x242 <main+0x2e>
 23a:	89 81       	ldd	r24, Y+1	; 0x01
 23c:	8f 5f       	subi	r24, 0xFF	; 255
 23e:	89 83       	std	Y+1, r24	; 0x01
 240:	f2 cf       	rjmp	.-28     	; 0x226 <main+0x12>
 242:	19 82       	std	Y+1, r1	; 0x01
 244:	f0 cf       	rjmp	.-32     	; 0x226 <main+0x12>
+1
А если на a = (a+1) % 16; заменить? Полагаю, останется только одна команда. Компилятор не такой уж глупый.
+1
Компилятор не такой уж глупый
Поддерживаю, когда-то проводил эксперимент с GCC (не помню, правда, под какую платформу). Оптимизатор заменял «остаток от деления на 2^х» на наложение советующей битовой маски.
0
a = (a+1) % 16;
 222:	89 81       	ldd	r24, Y+1	; 0x01
 224:	90 e0       	ldi	r25, 0x00	; 0
 226:	01 96       	adiw	r24, 0x01	; 1
 228:	8f 70       	andi	r24, 0x0F	; 15
 22a:	90 78       	andi	r25, 0x80	; 128
 22c:	89 83       	std	Y+1, r24	; 0x01
 22e:	f9 cf       	rjmp	.-14     	; 0x222 <main+0xe>

Все красиво. Но как мы понимаем, это частный случай)
0
хотя переменная «а» объявлена как uint8_t компилятор предпочел работу с int16_t. На его месте можно было бы и проще сделать:
222:   89 81           ldd     r24, Y+1        ; 0x01
 226:   01 96           subi    r24, 0xFF       ; 255
 228:   8f 70           andi    r24, 0x0F       ; 15
 22c:   89 83           std     Y+1, r24        ; 0x01
 22e:   f9 cf           rjmp    .-14            ; 0x222 <main+0xe>
0
Это С. Приходится понимать, что ты пишешь и как это поймет компилятор. В том числе и частные случаи, угу.
0
«C» здесь совершенно не причем))
0
Если уж так приспичило частные случаи использовать, то нужно и оформлять это как частный случай.
a = (a + 1) & (16 - 1)
0
Зачем? Может, я потом буду это на проц с бесплатным MOD компилировать.
_delay_ms в AVR-GCC, вон, вся на частных случаях и оптимизаторе основана.
0
Будьте внимательны это локальная переменная!!!
В данном случае нужна «атомарность» доступа к глобальной переменной и она обеспечивается.
Поправьте, если я не прав.
0
на порядок выполнения постинкремента в С полагаться нельзя

Ну, почему нельзя, можно… Просто многие думают, что постинкремент это особая операция, которая выполняется «потом» (многие думают что это «потом» находится в конце строки). На самом деле постинкремент как и другие подобные операции выполняется «сразу», просто происходит следующее – происходит инкремент аргумента но нам возвращается его старое значение. И мы дальше работаем со старым значением, хотя инкрементация уже произошла.

Это явно выражено в С++, там постинкремент это rvalue а преинкремент — lvalue
соответственно так можно
int * test = &(++a);

а так – ошибка
int * test = &(a++);
0
многие думают, что постинкремент это особая операция, которая выполняется «потом»
кстати, именно так и учат в основном, не вдаваясь в подробности… И до сего момента не задумывался об этом. Хотя раньше я писал на Delphi, где подобный изврат невозможен. Потому и практика осталась того, чтобы не пихать несколько действий в одну строку.
+1
кстати, именно так и учат в основном, не вдаваясь в подробности…

Да, есть такая проблема… По моим наблюдениям, очень многие люди, которое работают программистами, даже синьорами не знают достаточно простых вещей. Не знают приоритетов операций, не знают правил неявного приведения типов и т. д.

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


 const char * foo = “1234”;
что является константой: сам указатель или данные, размещенные по этому указателю? 



В каком подряде будут вызваны функции в каждом случае:
if(foo() && bar()) {};
int I = foo() + bar();


Сначала это мне казалось странным, потом привык :)
0
Видел вчера на 5 минут появлялась статейка с таким содержимым) Еще не дописана?
0
Это правильно, а то вдруг потенциальные кандидаты на собеседование тоже читают We.EE :)
0
ну а разве это плохо? Наоборот. Хуже те, кто не читают. Все мы в свое время кандидатами на собеседование являемся))
0
Ну одно дело обучающая информация, а другое — конкретные вопросы, на которые можно за полчаса до собеседования нагуглить ответ.
Я бы вот у e_mc2 точно не прошёл бы :(
0
Ну одно дело обучающая информация, а другое — конкретные вопросы, на которые можно за полчаса до собеседования нагуглить ответ.

Главное — это знания. И не столь важно как человек их получил (прочитав учебник или нагуглил за 5 мин до собеседования).

Я бы вот у e_mc2 точно не прошёл бы :(

Да ладно :) Я очень лоялен к кандидатам, задаю наводящие вопросы и т. д. (например «давайте вместе подумаем…» ). Я никогда не требую «заученных» знаний, типа перечисли мне все операторы С в порядке приоритета. Но, есть вещи, которые часто встречаются в коде, их знать очень желательно. Типа

int i = *ptr++;
Что произойдет (сначала взятие значения по адресу а потом инкремент этого значения,
или инкремент указателя, а потом взятие значения)?


int i = 3 * 1.5f;
Какие будут неявные приведения и каков конечный результат?
0
А вопроса с *p+1 у вас нет? :)
0
Тоже хороший вопрос :)
0
Да, случайно опубликовал раньше времени.
0
Объявления
char const* p2
const char* p3
это по разному записанное одно и то же объявление. Указатель на целое, которое нельзя менять. Сам же указатель константой не является.
char q=1;
const char *p;
p = &q; //на что указывает p можно менять
*p = 5; //ошибка, число менять уже нельзя

Обычно в реальных программах используется вариант объявления const int, а int const используется, чтобы запутать на собеседовании.

Чтобы везде были константы — нужно писать так:
const char* const p3
0
Ну, я не пытаюсь запутать. И даже не спрашиваю о char * const foo = «1234»; и прочих вариантах.

const char * foo = "1234";

— это самая распространенная конструкция, но, как оказалось, многие не знают к чему этот const относиться.
0
if(foo() && bar()) {};

Здесь слева направо, если foo() вернет 0, то bar() выполняться не будет
int I = foo() + bar();

Здесь сначала вызываются обе функции слева направо, а после производится сложение
0
Здесь сначала вызываются обе функции слева направо, а после производится сложение
Нет. В данном случае порядок вызова функций не определен, т к оператор «+» не является точкой следования
+1
и мой:
uint8_t NextIndex(uint8_t size, uint8_t index)
{
	index++;
	if (index >= size)
		index = 0;
	return index;
}

uint8_t Fifo_Full(Fifo_TypeDef *fifo)
{
	return NextIndex(FIFO_COUNT, fifo->TX_index) == fifo->RX_index;
}

uint8_t Fifo_Empty(Fifo_TypeDef *fifo)
{
	return fifo->RX_index == fifo->TX_index;
}

uint8_t Fifo_Read(Fifo_TypeDef *fifo, uint8_t* byte)
{
	if(!Fifo_Empty(fifo))
	{
		*byte = fifo->Buffer[fifo->RX_index];
		fifo->RX_index = NextIndex(FIFO_COUNT, fifo->RX_index);
		return 1;
	}
	return 0;
}
uint8_t Fifo_Write(Fifo_TypeDef *fifo, uint8_t byte)
{
	if(!Fifo_Full(fifo))
	{
		fifo->Buffer[fifo->TX_index] = byte;
		fifo->TX_index = NextIndex(FIFO_COUNT, fifo->TX_index);
		return 1;
	}
	return 0;
}
0
В итоговом варианте компилятору тоже позволено поменять порядок записи, только это маловероятно. И конвейер процессора тоже может поменять порядок. Но чем проще МК тем меньше вероятность, что порядок будет нарушен. Если возможны асинхронные вызовы то надо ставить барьер между записью символа и обновлением индекса.

На stm32 через dma хорошо uart получается использовать. И на передачу (блоками по переполнению буфера и периодически) и на прием (в циклический буфер).
0
В итоговом варианте компилятору тоже позволено поменять порядок записи, только это маловероятно

Насколько я понимаю, зависит от того как обвялены переменные. Если что-то в выражении
usart1_tx_buf[tx_buf_wr1]=c;

имеет «побочный эффект», то точка с запятой в конце будет точкой следования и изменить порядок компилятор не сможет… Или я ошибаюсь?
0
как показывает практика — точка с запятой не является точкой следования, и компилятор вполне может изменить порядок исполнения

int a = 1;
int b = 1;
a = a + 1;
b = b + 1;

может вполне исполнить в следующем порядке:

int a = 1;
a = a + 1;
int b = 1;
b = b + 1;

Не будем иметь ввиду сокращение этих строк как ненужных
0
как показывает практика — точка с запятой не является точкой следования

Она обязательно будет точкой следования в подобных выражениях, как только появятся побочные эффекты (иначе какие точки следования?). А в вашем коде побочных эффектов нет, поэтому компилятор может делать все что угодно.
Вот примеры с побочными эффектами, где точка с запятой является точкой следования и связывает руки оптимизатору:
DDRA =1;
PORTA =1;

Или
OpenFile();
WriteFile();
CloseFile();
Менять порядок компилятор не имеет права.
0
вызов функций он вообще не имеет права менять. Поэтому если нужен жесткий порядок в назначении переменных — как вариант — можно оформить в инлайн функции.
static inline uint16_t Inc(uint16_t a){return a+1}
static inline uint16_t Dec(uint16_t a){return a-1}

int main(void)
{
    uint16_t a = 0;
    uint16_t b = 2;
    a = Inc(a);
    b = Dec(b);
}
Выполнит вычисления в строгом порядке, как указано в программе без лишних сбросов в память (по типу volatale или барьер и прочее)
0
правда в данном случае горе-оптимизаторы, не видящие подвоха, могут оптимизировать код до
a++;
b++;
унеся вместе с этим и изначальную задумку.
0
b--
0
Скорее, горе-программисты. Если порядок присвоений важен — пишем volatile. При необходимости — еще и с барьерами.
+1
правда в данном случае горе-оптимизаторы, не видящие подвоха
Коллега Vga правильно написал.

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

uint16_t a = 1;
uint16_t b = 1;


Компилятор гарантирует лишь то, что четко детерминировано в стандарте С (советую с ним ознакомиться).
+1
ага, убедился уже. Протестировал. Действительно так не работает.
0
вызов функций он вообще не имеет права менять.
Нет, это миф. Может, я же приводи пример
int I = foo() + bar();

Компилятор действует по общим правилам (побочные эффекты, точки следования). Он считает, что функция имеет побочные эффекты кроме случаев, когда он может убедиться в том, что это «чистая» функция.
В Вашем примере с инлайн функциями он может убедиться, что побочных эффектов нет и дальше делать все что угодно.
0
что это «чистая» функция
Верне, в данном случае, что функция не обладает побочными эффектами (ибо «чистоста» подразумевает, дополнительно, что функция детерминирована)
0
Выполнит вычисления в строгом порядке, как указано в программе
Ой ли? Подставит и оптимизирует до MOV a, 1; MOV b, 1. Или MOV b, 1; MOV a, 1. На то он инлайн, чтобы подставляться и оптимизироваться.
+1
А что такое побочный эффект у переменных? Любая глобальная переменная или указатель неизвестно куда создают побочный эффект? По отношению к функциям из других объектов компиляции, да, можно так сказать. Запись в глобальную переменную нельзя переставить после вызова функции если запись была указана до него. Но если после записи в глобальную переменную указана еще одна такая запись в другую переменную? Или после записи вызывается функция из текущего объекта компиляции? Или функция объявлена как pure? Тут у компилятора руки уже развязываются. Я тоже могу ошибаться, не помню, чтобы у меня компилятор в таком случае менял порядок. Но всегда считал, что он может это делать.
0
А что такое побочный эффект у переменных?
Из того что точно помню – обращение к volatile переменной имеет побочный эффект. Касательно глобальных переменных – не уверен, нужно смотреть стандарт.
0
Меня, в данном случае, интересует такой вопрос.

Пусть tx_buf_wr1 объявлена как volatile, чтение этой переменной имеет побочный эффект.

usart1_tx_buf[tx_buf_wr1]=c;


у нас в конце строки будет точка следования. Гарантирует ли это, что до этого момента будет произведена запись в буфер, или это гарантирует только то, что переменная будет прочитана?
0
Поискал немного и напоролся вот на такой простой пример.

int A;
int B;


int foo() {
  
   A = B + 1;
   B = 0;
}

volatile помогает, только если обе переменных объявить с этим модификатором.
0
Да, судя по тому что нашел я, тоже следует, что в данном случае будет гарантировано только то, что до точки следования прочитается значение volatile переменной (tx_buf_wr1).
0
В комментах жду замечаний на тему «это каждому известно, на порядок выполнения постинкремента в С полагаться нельзя» и «ну ты и лох!».
Примерно так, только полагаться нельзя вообще на порядок операций между двумя точками следования. А их меньше, чем многие думают. Это еще не считая упомянутых выше конвееров
+1
  • avatar
  • Vga
  • 11 марта 2015, 23:05
Кстати, интересный ресурс есть: gcc.godbolt.org. On-line компилятор (изрядный список разных версий и платформ) и дизассемблер. На лету преобразует код в ассемблерный листинг. Несколько раз по случаю уже показывал знакомым какие весёлости может подкинуть компилятор при оптимизациях.
0
Использую для кольцевых буферов — пока проблем не было

bool RingBuffer_Put(RingBuffer_t * rb, uint8_t data)
{
	if (rb->Size != 0)
	{
		uint16_t next = rb->PutIndex + 1;
		if (next == rb->Size)
			next = 0;
		if (next != rb->GetIndex)
		{
			rb->pBuffer[rb->PutIndex] = data;
			rb->PutIndex = next;
			return true;
		}
	}
	return false;
}

bool RingBuffer_Get(RingBuffer_t * rb, uint8_t * data)
{
	if (rb->Size != 0)
	{
		uint16_t next = rb->GetIndex;
		if (next != rb->PutIndex)
		{
			*data = rb->pBuffer[next++];
			if (next == rb->Size)
				next = 0;
			rb->GetIndex = next;
			return true;
		}
	}
	*data = 0;
	return false;
}
0
  • avatar
  • x893
  • 12 марта 2015, 19:02
был подобный глюк как-то, там я не полностью инициализировал структуру DMA. В зависимости от фазы луны всё висло. Под отладчиком всё отлично, т.к. он обнулял ОЗУ при заливке прошивки.

Может, KEIL и ни при чём? Сам тогда пользовался дебиановским ARM-GCC.
0
Мьютексы для лохов.
-2
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.