|
Неожиданный результат, Прочитать два байта |
|
|
|
Jan 19 2008, 14:37
|
Знающий
   
Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447

|
Долго не мог выявить, почему цифровой датчик давления выдает неверный результат. Код //Первый случай; resultat = ((unsigned int)i2c_read(ACK)<<8) | i2c_read(ACK);
//Второй случай: HiTemp=i2c_read(ACK); LowTemp=i2c_read(ACK); resultat = ((unsigned int)HiTemp<<8) | LowTemp; Во втором случае дает другой (правильный) результат. Почему?
|
|
|
|
|
 |
Ответов
|
Jan 19 2008, 14:45
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(alux @ Jan 19 2008, 16:37)  Во втором случае дает другой (правильный) результат. Почему? Потому что стандарт языка С не определяет порядок вычисления подвыражений. Какой из i2c_read() будет вызван первым зависит в общем случае от положения звезд на небе. Ваша функция обладает побочными эффектами, поэтому вызывать ее дважды в одном выражении не рекомендуется во избежание таких неожиданностей.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Jan 20 2008, 12:17
|

Местный
  
Группа: Свой
Сообщений: 222
Регистрация: 9-06-07
Пользователь №: 28 317

|
Цитата(Сергей Борщ @ Jan 19 2008, 17:45)  Потому что стандарт языка С не определяет порядок вычисления подвыражений. Что значит, стандарт не определяет? Вот строчка из "K&R C": "Все бинарные операции и условная операция (прим. Перевод.: условная операция группируется справа налево; это изменение внесено в язык в 1978 г.) группируются слева направо" Это следут проверить таким образом: Код resultat = ((HiTemp = (unsigned int)i2c_read(ACK))<<8) | (LowTemp = i2c_read(ACK));
printf("High byte 0x%02X\n", HiTemp); printf("Low byte 0x%02X\n", LowTemp);
|
|
|
|
|
Jan 20 2008, 18:59
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(Aesthete Animus @ Jan 20 2008, 14:17)  Вот строчка из "K&R C": "Все бинарные операции и условная операция (прим. Перевод.: условная операция группируется справа налево; это изменение внесено в язык в 1978 г.) группируются слева направо" Если под бинарными понимаются && и || - определен. Для остальных (кроме ?: и оператора "запятая") - нет. Вот цитата из стандарта языка (INTERNATIONAL STANDARD ISO/IEC 9899, Programming languages — C, Second edition 1999-12-01): Цитата 6.5 Expressions .... 3 The grouping of operators and operands is indicated by the syntax. Except as specified later (for the function-call (), &&, ||, ?:, and comma operators), the order of evaluation of subexpressions and the order in which side effects take place are both unspecified. Это дает гораздо больше свободы оптимизатору. Цитата(alux @ Jan 20 2008, 17:01)  Вот что выдал листинг: Мне пришло в голову, что во избежание подобных недоразумений функцию надо бы объявить как volatile. В этом случае: 1) Компилятор был бы обязан вызвать ее дважды. 2) IAR выдал бы предупреждение о неопределенности при использовании более одной volatile в выражении. GCC такое предупреждение не выдает, это ему минус. Хотя когда в IAR появилась эта проверка - многих предупреждение ставило в тупик. Вот, видимо, наглядный пример, когда это предупреждение обосновано.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Jan 20 2008, 22:07
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(Сергей Борщ @ Jan 20 2008, 21:59)  Мне пришло в голову, что во избежание подобных недоразумений функцию надо бы объявить как volatile. В этом случае: 1) Компилятор был бы обязан вызвать ее дважды. Даже без volatile компилятор обязан был вызвать эту функцию дважды, т.к. внутри этой функции есть работа с volatile переменными, например с регистрами TWI... P.S. Нда... , чего-то у IAR совсем плохо c volatile..., уже 2 баги за последние пару дней...
|
|
|
|
|
Jan 21 2008, 07:39
|
Гуру
     
Группа: Свой
Сообщений: 2 702
Регистрация: 14-07-06
Пользователь №: 18 823

|
Цитата(singlskv @ Jan 21 2008, 02:07)  Даже без volatile компилятор обязан был вызвать эту функцию дважды, т.к. внутри этой функции есть работа с volatile переменными, например с регистрами TWI...
P.S. Нда... , чего-то у IAR совсем плохо c volatile..., уже 2 баги за последние пару дней... Ничего он не обязан. Кто писал программу - Вы или компилятор? Все свойства функции - это типы параметров и адрес вызова, а требовать от компилятора анализа свойств используемых переменных внутри функции - эта попытка взвалить на него исправление Ваших ошибок.
--------------------
Уходя, оставьте свет...
|
|
|
|
|
Jan 21 2008, 20:54
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(Dog Pawlowa @ Jan 21 2008, 10:39)  Ничего он не обязан. Кто писал программу - Вы или компилятор? Все свойства функции - это типы параметров и адрес вызова, а требовать от компилятора анализа свойств используемых переменных внутри функции - эта попытка взвалить на него исправление Ваших ошибок. Атлична, раскажите мне пожалуйста, исходя из каких правил описанных в стандарте С компилятор имеет право обработать эти две ситуации по разному: Код int NegPort() { PORTB~=0xFF; return 1; }
....... a = NegPort() + NegPort(); и Код a = NegPort(); a+=NegPort(); по Вашей логике в первом случае он имеет право вызвать NegPort() один раз а во втором обязян вызвать 2 раза... и почему во втором случае он не вызовет так же один раз ? О порядке вызова функций при сложении речи не идет... P.S. Пример конечно сильно упрощен, но смысл я надеюсь понятен... P.P.S А так же в догонку очень хотелось бы понять, имеет ли право на жизнь следующая запись в языке С: Z = X * K1 * rand() + Y * K2 * rand(); или я обязан разделить ее на 2 подвыражения для того чтобы не получить одинаковый результат на выходе rand().... ???
|
|
|
|
|
Jan 22 2008, 08:50
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(Сергей Борщ @ Jan 22 2008, 02:13)  Ведь тело функции может находиться и в другой единице компиляции. Откуда в таком случае компилятор узнает, что вы в ней обращаетесь к volatille-объектам? Я полагаю, что для получения правильного поведения функция должна быть описана как int NegPort() voltile {}. Хорошо, есть функция rand() которая определена в другой единице компиляции, в стандартной библиотеке. Определена она так int rand(void) , т.е. без всяких volatile. Вопрос: Могу ли я написать у себя в программе А = rand() + rand(); ? или компилятор может вызвать rand только один раз ?
|
|
|
|
|
Jan 22 2008, 11:57
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
Цитата(singlskv @ Jan 22 2008, 10:50)  Вопрос: Могу ли я написать у себя в программе А = rand() + rand(); ? или компилятор может вызвать rand только один раз ? Проконсультировался с ReAl - в С функция не может иметь атрибута volatile, это я с плюсами попутал: Цитата Но в любом случае функция "по определению" может иметь побочные эффекты (как та же getchar() - её побочный эффект - изменение некоего файлового об'екта, аппаратного окружения, ...) и компилятор вообще говоря не имеет права заменить два вызова функции одним. Ну, за исключением работы оптимизатора, "видящего" тело функции и решившего, что это можно сделать - inline-функции в С99 или компиляция всей программы как одного файла ( --whole-program *.c для gcc ). В gcc можно дать атрибут функции - "результат зависит только от переданных аргументов, больше никуда функция не смотрит и ничего снаружи не меняет", тогда несколько её вызовов с одинаковыми аргументами оптимизатор заменяет на один. Так что тут вы правы - компилятор не может заменить два i2c_read() или NegPort() на один. Признаю, вводил в заблуждение. Это заставило вновь внимательно посмотреть листинг из поста №7 - и что мы видим? Мы не видим там одного вызова i2c_read() - мы видим, что вызов(ы) i2c_read() происходят в подпрограмме ?Subroutine0, содержимое которой нам не показали. Видимо подобная конструкция используется в файле не один раз и компилятор посчитал оптимальным вынести ее в отдельную подпрограмму. Ставлю себе большую двойку за невнимательность. Так что вопрос - ошибся тут компилятор или нет остается открытым до обнародования листинга ?Subroutine0
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Jan 22 2008, 12:44
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(Сергей Борщ @ Jan 22 2008, 14:57)  Это заставило вновь внимательно посмотреть листинг из поста №7 - и что мы видим? Мы не видим там одного вызова i2c_read() - мы видим, что вызов(ы) i2c_read() происходят в подпрограмме ?Subroutine0, содержимое которой нам не показали. Видимо подобная конструкция используется в файле не один раз и компилятор посчитал оптимальным вынести ее в отдельную подпрограмму. Ставлю себе большую двойку за невнимательность. Так что вопрос - ошибся тут компилятор или нет остается открытым до обнародования листинга ?Subroutine0 Возможно Вы правы насчет объединения двух вызовов в отдельную подпраграмму, только тогда очень странно выглядит LDI R25,0 после подпрограммы... Автор, выложите более полный листинг.
|
|
|
|
|
Jan 22 2008, 13:31
|
Знающий
   
Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447

|
Цитата(singlskv @ Jan 22 2008, 16:44)  Автор, выложите более полный листинг. Пожалуйста. Не подозревал, что простой, на первый взгляд, вопрос вызовет такое бурное обсуждение  Добавлено
Прошу прощения, но теперь в данном lst функция i2c_read(ACK) вызывается дважды. Хотя готов поклясться, что в посте №7 функция вызывалась один раз. Попытаюсь воспроизвести ту ситуацию еще раз. Листинги я не сохранил, к сожалению. Еще раз добавлено
Вот , восстановил (asdx-2). Возможно я был не внимательный, когда говорил, что функция вызывается один раз. Кстати, вопросик по ходу: как лучше писать Код Accumulator = ((unsigned int)i2c_read(ACK)<<8) | i2c_read(ACK); или Accumulator = ((unsigned int)i2c_read(ACK)<<8) + i2c_read(ACK); В последний раз добавлено 
Прокоментируйте, пожалуйста следующее: Код 49 Accumulator = ((unsigned int)i2c_read(ACK)<<8) | i2c_read(ACK); \ 00000022 ........ CALL i2c_readAck \ 00000026 ........ CALL i2c_readAck \ 0000002A 2F80 MOV R24, R16 \ 0000002C E090 LDI R25, 0 50 i2c_stop(); \ 0000002E ........ CALL i2c_stop Возможно, здесь нарушен порядок вызовов функций: первым должен читаться старший байт, затем сдвинуться на 8 бит, затем сложить с прочитанным младшим байтом.
Прикрепленные файлы
asdx.rar ( 1.38 килобайт )
Кол-во скачиваний: 29
asdx_2.rar ( 1.72 килобайт )
Кол-во скачиваний: 27
|
|
|
|
|
Jan 22 2008, 17:53
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(alux @ Jan 22 2008, 16:31)  Прошу прощения, но теперь в данном lst функция i2c_read(ACK) вызывается дважды. Хотя готов поклясться, что в посте №7 функция вызывалась один раз. Попытаюсь воспроизвести ту ситуацию еще раз. Листинги я не сохранил, к сожалению.
Еще раз добавлено Вот , восстановил (asdx-2). Возможно я был не внимательный, когда говорил, что функция вызывается один раз. К сожалению Вы востановили не до конца, перед обсуждаемыми строками у Вас еще стоят: 44 HiTemp=i2c_read(ACK); 45 LowTemp=i2c_read(NACK); которые и мешают отработать оптимизатору так же как и было в самом первом листинге... Цитата Кстати, вопросик по ходу: как лучше писать Код Accumulator = ((unsigned int)i2c_read(ACK)<<8) | i2c_read(ACK); или Accumulator = ((unsigned int)i2c_read(ACK)<<8) + i2c_read(ACK); ИМХО, в данном случае пофиг, но в общем и в целом лучше в таких случаях пользоваться |, так и вероятность ошибок меньше и скорость работы может быть выше, все зависит от конкретного компилятора. Цитата Прокоментируйте, пожалуйста следующее: Код 49 Accumulator = ((unsigned int)i2c_read(ACK)<<8) | i2c_read(ACK); \ 00000022 ........ CALL i2c_readAck \ 00000026 ........ CALL i2c_readAck \ 0000002A 2F80 MOV R24, R16 \ 0000002C E090 LDI R25, 0 50 i2c_stop(); \ 0000002E ........ CALL i2c_stop Возможно, здесь нарушен порядок вызовов функций: первым должен читаться старший байт, затем сдвинуться на 8 бит, затем сложить с прочитанным младшим байтом. Порядок то здесь наверняка нарушен(наверное мы чуть запутали Вас нашим обсуждением), в Вашем случае все равно нужно делить эти вызовы на два разных оператора, те первый ответ данный Сергей Борщ был абсолютно правильный. Тока вот все равно какая то фигня творится, LDI R25,0 здесь просто не должно быть...
|
|
|
|
Сообщений в этой теме
alux Неожиданный результат Jan 19 2008, 14:37 alux Я поражен меткости и, главное, оперативности Ваших... Jan 19 2008, 14:53   Aesthete Animus Цитата(Сергей Борщ @ Jan 20 2008, 21:59) ... Jan 20 2008, 19:43    Сергей Борщ Цитата(Aesthete Animus @ Jan 20 2008, 21... Jan 20 2008, 21:34             alux Код 49 Accumulator = ((unsigned int... Jan 22 2008, 19:47             ReAl Цитата(alux @ Jan 22 2008, 15:31) Кстати,... Jan 22 2008, 21:17              alux Цитата(ReAl @ Jan 23 2008, 01:17) У IAR-а... Jan 23 2008, 07:13               ReAl Я немного цитаты перетасую
Цитата(alux @ Jan... Jan 23 2008, 14:49                alux Цитата(ReAl @ Jan 23 2008, 18:49) в преды... Jan 23 2008, 15:10                 Сергей Борщ Цитата(alux @ Jan 23 2008, 17:10) ReAl ог... Jan 23 2008, 15:30                  ReAl Цитата(Сергей Борщ @ Jan 23 2008, 17:30) ... Jan 24 2008, 08:04 Baser Цитата(alux @ Jan 19 2008, 16:37) Долго н... Jan 19 2008, 15:44 rezident Я не такой большой специалист в Си и могу ошибатьс... Jan 20 2008, 12:23 alux Цитата(rezident @ Jan 20 2008, 16:23) ...... Jan 20 2008, 15:01
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|