Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Деление int на size_t
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > AVR
sonycman
Возникла непонятка.
Использую деление переменной типа int на значение, возвращаемое функцией sizeof():

int t;
...
t = t / sizeof(buf);
if (t < 0) ...
else ...

Так вот - знак t может быть различный, но после деления на sizeof() компилятор считает, что знак результата становится положительный и отбрасывает ветку кода для обработки отрицательного значения. Даже функция деления используется беззнаковая.
Если вместо sizeof() подставить константу, то всё начинает работать как положено.

Что я делаю не так, подскажите?
Xenia
Цитата(sonycman @ Nov 6 2013, 14:37) *
int t;
...
t = t / sizeof(buf);
if (t < 0) ...
else ...


Результат sizeof() - беззнаковый, оттого и все выражение становится таким. Приведите его явно к знаковому типу, и все устаканится:
t /= (int)sizeof(buf);
sonycman
Цитата(Xenia @ Nov 6 2013, 15:24) *
Результат sizeof() - беззнаковый, оттого и все выражение становится таким.

Понятно, мпасибо.
Но разве не должно быть наоборот - если хоть один операнд знаковый, то результат становится знаковым?
V_G
Если перейдете на С++ (язык с более строгим контролем соответствия типов данных) , то компилятор по-хорошему должен выдать сообщение об ошибке и потребовать явного приведения к типу. Всякого рода умолчания тут вредны.
mdmitry
Цитата(Xenia @ Nov 6 2013, 15:24) *
Результат sizeof() - беззнаковый, оттого и все выражение становится таким. Приведите его явно к знаковому типу, и все устаканится:
t /= (int)sizeof(buf);

А не боитесь, что sizeof(buf) станет отрицательным. На "малых" значениях sizeof(buf) положительный и знак определяется только t, для "больших" значений sizeof(buf) отрицательный, и результат зависит от обоих знаков.
sonycman
Программа и так компилируется в плюсах, какого-то специального варнинга не видел.
Если вместо sizeof() использовать const unsigned char size = 20, то проблем со знаком нет, результат получается со знаком.
Не глюк ли это компилятора? Как еще это можно обьяснить?
Xenia
Цитата(mdmitry @ Nov 6 2013, 18:20) *
А не боитесь, что sizeof(buf) станет отрицательным. На "малых" значениях sizeof(buf) положительный и знак определяется только t, для "больших" значений sizeof(buf) отрицательный, и результат зависит от обоих знаков.


Ничуть не боюсь sm.gif, т.к. если делитель вылез из разрядной сетки целого, которая оказалась для него тесновата, а делимое t поместилось, то вряд ли можно получить какое-то частное, кроме нуля.

Опять же buf размером в 2 гигабайта на AVR-микроконтроллере едва ли возможен. sm.gif
zhevak
Цитата(sonycman @ Nov 6 2013, 21:11) *
Программа и так компилируется в плюсах, какого-то специального варнинга не видел.
Если вместо sizeof() использовать const unsigned char size = 20, то проблем со знаком нет, результат получается со знаком.
Не глюк ли это компилятора? Как еще это можно обьяснить?

здесь у Вас unsigned char (8 бит), а t имеет тип int (16 бит).
И надо учитывать, что sizeof возвращает не unsigned char, и даже не unsigned int, а size_t.

Думаю, Вам надо посмотреть как конкретно на вашем компиляторе реализуется тип size_t.
AHTOXA
Цитата(sonycman @ Nov 6 2013, 21:11) *
Программа и так компилируется в плюсах, какого-то специального варнинга не видел.

Если там gcc, то за это предупреждение отвечает флаг -Wsign-compare.
sonycman
Цитата(V_G @ Nov 6 2013, 18:00) *
Если перейдете на С++ (язык с более строгим контролем соответствия типов данных) , то компилятор по-хорошему должен выдать сообщение об ошибке и потребовать явного приведения к типу. Всякого рода умолчания тут вредны.

Это именно C++.
А в чём именно ошибка? Int делим на Short (или любой другой тип без знака). В результате будет Int. Это ошибка?

Цитата(zhevak @ Nov 6 2013, 20:47) *
И надо учитывать, что sizeof возвращает не unsigned char, и даже не unsigned int, а size_t.

Функция у sizeof() - вернуть константу, то есть самое обычное число без знака. Каким образом её результат может влиять на знак выражения типа: -100/х = y?

Цитата(AHTOXA @ Nov 6 2013, 20:59) *
Если там gcc, то за это предупреждение отвечает флаг -Wsign-compare.

Проверил на других компиляторах: IAR и CodeVision не страдают этой ошибкой и генерят код в соответствии с математическими правилами и моими ожиданиями.
А вот GCC в AtmelStudio и для ARMов точно также отбрасывает знак и считает, что результат может быть только положительный.
Прискорбно sad.gif
V_G
Цитата(sonycman @ Nov 7 2013, 08:38) *
Это именно C++.
А в чём именно ошибка? Int делим на Short (или любой другой тип без знака). В результате будет Int. Это ошибка?

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

PS. Short - не обязательно беззнаковый тип
AHTOXA
Цитата(sonycman @ Nov 7 2013, 04:38) *
А вот GCC в AtmelStudio и для ARMов точно также отбрасывает знак и считает, что результат может быть только положительный.
Прискорбно sad.gif

Это не прискорбно, это - в соответствии со стандартом.
Вот что говорится в ISO/IEC 9899:1999 6.3.1.8 Usual arithmetic conversions:
Цитата
Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
  • If both operands have the same type, then no further conversion is needed.
  • Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
  • Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
  • Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then
    the operand with unsigned integer type is converted to the type of the operand with signed integer type.
  • Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.


Ваш вариант - последний (поскольку int не может вместить всех значений size_t, size_t - не может вместить всех значений int, то побеждает size_t, как беззнаковый.)
zhevak
... и еще вот тут есть интересные рассуждения про size_t

http://www.embedded.com/electronics-blogs/...-size-t-matters

Переведу-ка я ее на русский, да опубликую не сегодня-завтра в своем блоге.
Полезное ведь дело, однако, да.
sonycman
Цитата(AHTOXA @ Nov 7 2013, 07:59) *
Ваш вариант - последний (поскольку int не может вместить всех значений size_t, size_t - не может вместить всех значений int, то побеждает size_t, как беззнаковый.)

Тогда обязан был быть ворнинг или даже еррор, так как в результате такого преобразования разрушается значение Int.
Но никакого предупреждения компилер не дает.

Вероятно, более продвинутые компиляторы понимают, что в моём случае sizeof() даёт размерность, которая умещается в простом char, и, соответственно, не преобразуют Int.
Для GCC это непосильная задача sad.gif
AHTOXA
Цитата(sonycman @ Nov 7 2013, 14:47) *
Вероятно, более продвинутые компиляторы понимают, что в моём случае sizeof() даёт размерность, которая умещается в простом char, и, соответственно, не преобразуют Int.
Для GCC это непосильная задача sad.gif

1. sizeof() возвращает size_t, независимо от значения.
2. int/size_t возвращает size_t. (при условии sizeof(int) == sizeof(size_t) )
Это требование стандарта.
Вы считаете, что несоблюдение компилятором стандарта языка говорит о том, что этот компилятор "более продвинутый"? sm.gif

Кстати, есть вариант, что в этих продвинутых компиляторах int просто длиннее, чем size_t, и поэтому всё получается нормально без нарушения стандарта.
sonycman
Цитата(AHTOXA @ Nov 7 2013, 13:30) *
1. sizeof() возвращает size_t, независимо от значения.
2. int/size_t возвращает size_t. (при условии sizeof(int) == sizeof(size_t) )
Это требование стандарта.
Вы считаете, что несоблюдение компилятором стандарта языка говорит о том, что этот компилятор "более продвинутый"? sm.gif

Кстати, есть вариант, что в этих продвинутых компиляторах int просто длиннее, чем size_t, и поэтому всё получается нормально без нарушения стандарта.


А что, Вы будете спорить, что IAR и прочие коммерческие компилеры - более продвинутые?
Это видно и на моём примере sm.gif
А в защиту GCC можно сказать только - что он "блюдёт" стандарты. sm.gif
AHTOXA
Вот уж не ожидал, что приведённое мной объяснение может вызвать такую реакцию.
Подумайте ещё вот о чём. Существует ненулевая вероятность, что в какой-то из следующих версий IAR данное несоответствие стандарту устранят... sm.gif
sonycman
Цитата(AHTOXA @ Nov 7 2013, 15:17) *
Вот уж не ожидал, что приведённое мной объяснение может вызвать такую реакцию.
Подумайте ещё вот о чём. Существует ненулевая вероятность, что в какой-то из следующих версий IAR данное несоответствие стандарту устранят... sm.gif

Да обычная реакция. Когда в очередной раз слышишь, что маразм из разряда -2/1=2 - это стандарт.

Не только IAR "игнорирует" ваш стандарт, но и CodeVisionAVR.
Я еще Keil не проверял, возможно, он тоже из разряда "недоделанных" sm.gif

Может быть, отличия в результатах компиляции - вовсе не различная трактовка стандартов, а работа оптимизации?
AHTOXA
Это не мой стандарт, это стандарт языка Си.
И лучше такой стандарт, чем никакого. Странно, что вы, вроде бы достаточно опытный разработчик, этого не понимаете.
А уж ссылки на CodeVisionAVR - это вообще нонсенс, поскольку давно известно, что CodeVisionAVR - это компилятор "языка, похожего на Си":)
Вы лучше проверьте "взрослые" компиляторы.
(И посмотрите в IAR, чему там равны sizeof(int) и sizeof(size_t), вдруг и IAR соблюдает стандарт).
sonycman
Цитата(AHTOXA @ Nov 7 2013, 20:33) *
(И посмотрите в IAR, чему там равны sizeof(int) и sizeof(size_t), вдруг и IAR соблюдает стандарт).

Оба равны двум, что неудивительно. Только у одного 2+2 равно 4, а у второго - 5.
Думаете, какой из них больше соблюдает стандарт?

ЗЫ: сейчас докачаю Кейл и проверю там, на АРМ.

ЗЗЫ: Упс, обновил AtmelStudio на последнюю версию 6.1 (стояла 6.0), ничего в тексте не менял, пересобрал проект - появилось знаковое деление и обработка отрицательных значений.
То есть исправили багу и теперь поведение GCC стало аналогичным IAR.
Попробуйте теперь объяснить, как необходимо правильно трактовать разные умные стандарты laughing.gif

Ставить Кейл пока не стану, уверен, что его код будет аналогичен IAR.
AHTOXA
Цитата(sonycman @ Nov 8 2013, 03:31) *
Оба равны двум, что неудивительно.

Да всяко бывает. Бывает 4 (на АРМ-ах). Бывает и 8 (на 64-битных системах). Ещё бывает, что размер int и size_t разный.
Цитата(sonycman @ Nov 8 2013, 03:31) *
Только у одного 2+2 равно 4, а у второго - 5. Думаете, какой из них больше соблюдает стандарт?

Больше соблюдает тот, у которого результат соответствует стандарту. Если по стандарту должно получиться 5, то второй sm.gif
Цитата(sonycman @ Nov 8 2013, 03:31) *
ЗЗЫ: Упс, обновил AtmelStudio на последнюю версию 6.1 (стояла 6.0), ничего в тексте не менял, пересобрал проект - появилось знаковое деление и обработка отрицательных значений.
То есть исправили багу и теперь поведение GCC стало аналогичным IAR.
Попробуйте теперь объяснить, как необходимо правильно трактовать разные умные стандарты laughing.gif

Очень рад за вас sm.gif
Не знаю, почему так. Возможно, здесь константа вычисляется во время компиляции, и поэтому она трактуется как нетипизированная.
Для интереса попробуйте написать функцию, которая возвращает size_t, и поделить int на результат этой функции.
sonycman
Прошу прощения, поторопился я вчера с выводами, поздно было, допустил невнимательность и рано обрадовался sad.gif
Не заметил приведение типов - нет, версия студии 6.1 всё так же остаётся верна стандартам и сама себе - знак так же отбрасывается, как и раньше.

Зато потестил Кейл - как и предполагал, работает по аналогии с IAR - чихает на стандарты, делит со знаком и его дальнейшей обработкой. Наш человек sm.gif

Подставлял в IAR вместо sizeof() переменную с типом size_t - без разницы, никакого превращения Int в беззнаковый тип не происходит, всё работает как и раньше.
zhevak
Да. Было бы любопытно посмотреть на распечатку ассеблерного кода в том и другом случае.
sonycman
Цитата(zhevak @ Nov 8 2013, 15:29) *
Да. Было бы любопытно посмотреть на распечатку ассеблерного кода в том и другом случае.

Вы про IAR? Уточните, пожалуйста.
Смогу предоставить ассемлерный листинг только ночью или завтра, к сожалению.
zhevak
Цитата(sonycman @ Nov 8 2013, 18:02) *
Вы про IAR? Уточните, пожалуйста.
Смогу предоставить ассемлерный листинг только ночью или завтра, к сожалению.

Честно говоря, я не думал про какой-то конкретный компилятор.
Я под Линем сижу, могу притащить сюда листин от gcc. (Если кто-нибудь меня не опередит к этому времени, гы-гы!)

Давайте какой-нибудь простенький код обуликуем, откомпилируем и сравним. Внимание нужно будет уделить именно арифметическим командам проца, которые как раз занимаются вычислением деления int на size_t.

Ну что, попробуем сделать?
sonycman
Без проблем, только я сейчас на работе, и смогу добраться до компа только к ночи, и то не надолго.
Думаете, стоит расмотреть только деление, или пройтись по остальным операциям?
Хотя разницы не будет, вероятнее всего.

Постараюсь привести пример с делением и его код из под трех компиляторов - GCC, IAR и Keil. Последний под ARM.
ILYAUL
Коллеги , а давайте в отдельной теме. Эта всё таки про студию
IgorKossak
Цитата(ILYAUL @ Nov 8 2013, 16:13) *
Коллеги , а давайте в отдельной теме. Эта всё таки про студию

Разделил.
Модератор
sonycman
Несколько устав уже от нюансов по поводу интерпретации стандатров различными компиляторами, нашёл всё таки время "добить" код, прогнав его через IAR и Keil.
Результаты не совсем однозначные, но вот они, какие есть.

Исходный код:
Код
    volatile int tbuf[20], result;
    while (1)
    {
        int t = 0;
        for (unsigned char c = 0; c < sizeof(tbuf)/sizeof(int); c++)
        {
            t += tbuf[c];
        }
        
        t /= sizeof(tbuf)/sizeof(int);            //деление INT на SIZE_T, приводящее к "тихой" ошибке
        
        result = t < 0 ? t/5 : t;

    }

Генерируется он на всех трёх компиляторах тихо, без ошибок и предупреждений, хотя несёт в себе ошибку, превращающую INT при делении в беззнаковый тип, отбрасывая таким образом все отрицательные значения.
При этом GCC полностью удаляет из полученного кода часть, которая обрабатывает отрицательный результат. В IAR и Keil она присутствует, хотя выполнена может быть далеко не всегда.

Полученный код на GCC (Atmel Studio 6.1):
CODE

6a: 60 e0 ldi r22, 0x00 ; 0
6c: 70 e0 ldi r23, 0x00 ; 0
6e: 80 e0 ldi r24, 0x00 ; 0
70: 90 e0 ldi r25, 0x00 ; 0
72: fb 01 movw r30, r22
74: ee 0f add r30, r30
76: ff 1f adc r31, r31
78: 21 e0 ldi r18, 0x01 ; 1
7a: 30 e0 ldi r19, 0x00 ; 0
7c: 2c 0f add r18, r28
7e: 3d 1f adc r19, r29
80: e2 0f add r30, r18
82: f3 1f adc r31, r19
84: 20 81 ld r18, Z
86: 31 81 ldd r19, Z+1 ; 0x01
88: 82 0f add r24, r18
8a: 93 1f adc r25, r19
8c: 6f 5f subi r22, 0xFF ; 255
8e: 7f 4f sbci r23, 0xFF ; 255
90: 64 31 cpi r22, 0x14 ; 20
92: 71 05 cpc r23, r1
94: 71 f7 brne .-36 ; 0x72 <main+0x1c>
96: 03 d0 rcall .+6 ; 0x9e <__udivmodhi4>
98: 7a a7 std Y+42, r23 ; 0x2a
9a: 69 a7 std Y+41, r22 ; 0x29
9c: e6 cf rjmp .-52 ; 0x6a <main+0x14>


Код IAR (v6.21):
CODE

\ ??main_1:
\ 00000004 E045 LDI R20, 5
\ 00000006 E050 LDI R21, 0
\ 00000008 .... RCALL ?SS_DIVMOD_L02
\ ??main_2:
\ 0000000A 8308 ST Y, R16
\ 0000000C 8319 STD Y+1, R17
\ ??main_0:
\ 0000000E E000 LDI R16, 0
\ 00000010 E010 LDI R17, 0
\ 00000012 E020 LDI R18, 0
\ ??main_3:
\ 00000014 2FEC MOV R30, R28
\ 00000016 5FEE SUBI R30, 254
\ 00000018 2F32 MOV R19, R18
\ 0000001A 0F33 LSL R19
\ 0000001C 0FE3 ADD R30, R19
\ 0000001E 8140 LD R20, Z
\ 00000020 8151 LDD R21, Z+1
\ 00000022 0F04 ADD R16, R20
\ 00000024 1F15 ADC R17, R21
\ 00000026 9523 INC R18
\ 00000028 3124 CPI R18, 20
\ 0000002A F3A0 BRCS ??main_3
\ 0000002C E144 LDI R20, 20
\ 0000002E E050 LDI R21, 0
\ 00000030 .... RCALL ?SS_DIVMOD_L02
\ 00000032 2311 TST R17
\ 00000034 F33A BRMI ??main_1
\ 00000036 CFE9 RJMP ??main_2

Здесь уже ошибки нет, применяется деление со знаком.
Однако, если скомпилировать не Release код, а Debug, при тех же настройках оптимизации, получается код с беззнаковым делением.
Интересно 05.gif

Кейл (uVision v4.72) тоже применяет деление без знака, как и GCC, хотя мне вначале казалось наоборот. Прошу простить за невнимательность smile3046.gif
Код
00000c  2314              MOVS     r3,#0x14
00000e  466a              MOV      r2,sp                ;33
;;;37             
;;;38             result = t < 0 ? t/5 : t;
000010  2405              MOVS     r4,#5
                  |L1.18|
000012  2100              MOVS     r1,#0                ;30
000014  ea4f0001          MOV.W    r0,r1                ;31
                  |L1.24|
000018  f8525020          LDR      r5,[r2,r0,LSL #2]    ;33
00001c  1c40              ADDS     r0,r0,#1             ;31
00001e  4429              ADD      r1,r1,r5             ;33
000020  2814              CMP      r0,#0x14             ;31
000022  d3f9              BCC      |L1.24|
000024  fbb1f0f3          UDIV     r0,r1,r3             ;36
000028  2800              CMP      r0,#0
00002a  da01              BGE      |L1.48|
00002c  fb90f0f4          SDIV     r0,r0,r4
                  |L1.48|
;;;39    
;;;40         }
000030  9014              STR      r0,[sp,#0x50]
000032  e7ee              B        |L1.18|

Логика несколько неясна, после UDIV проверять значение на отрицательный знак? Это возможно разве?
Но не перестаю восхищаться красивым, мощным, ёмким и компактным кодом Cortex ARM. Он в два раза короче, чем код для AVR! disco.gif
maksimp
Цитата(sonycman @ Nov 9 2013, 22:56) *
При этом GCC полностью удаляет из полученного кода часть, которая обрабатывает отрицательный результат.

Чудеса оптимизации. Умеет определять, что после деления 0...65535 на 20 получится 0...3276, после преобразования к знаковому будет не отрицательным у ветка для t<0 не нужна.
Цитата(sonycman @ Nov 9 2013, 22:56) *
Код IAR (v6.21):
...
Здесь уже ошибки нет, применяется деление со знаком.
Однако, если скомпилировать не Release код, а Debug, при тех же настройках оптимизации, получается код с беззнаковым делением.
Интересно 05.gif

Увы, IAR не вызывает доверия. Самый худший вариант, когда результат зависит от включения отладки sad.gif.
defunct
Цитата(sonycman @ Nov 6 2013, 15:25) *
Но разве не должно быть наоборот - если хоть один операнд знаковый, то результат становится знаковым?

не должно быть наоборот. Результат приводится к большей разрядной сетке.
unsigned имеет больше значащих разрядов чем signed.
Genadi Zawidowski
Ну написано же в стандарте явно про беззнаковость size_t...
Цитата
3.3.3.4 The sizeof operator

Constraints

The sizeof operator shall not be applied to an expression that has
function type or an incomplete type, to the parenthesized name of such
a type, or to an lvalue that designates a bit-field object.

Semantics

The sizeof operator yields the size (in bytes) of its operand,
which may be an expression or the parenthesized name of a type. The
size is determined from the type of the operand, which is not itself
evaluated. The result is an integer constant.

When applied to an operand that has type char , unsigned char , or
signed char , (or a qualified version thereof) the result is 1. When
applied to an operand that has array type, the result is the total
number of bytes in the array./35/ When applied to an operand that has
structure or union type, the result is the total number of bytes in
such an object, including internal and trailing padding.

The value of the result is implementation-defined, and its type (an
unsigned integral type) is size_t defined in the <stddef.h> header.



И про преобразование перед делением:
Цитата
3.2.1.5 Usual arithmetic conversions

Many binary operators that expect operands of arithmetic type cause
conversions and yield result types in a similar way. The purpose is
to yield a common type, which is also the type of the result. This
pattern is called the usual arithmetic conversions: First, if either
operand has type long double, the other operand is converted to long
double . Otherwise, if either operand has type double, the other
operand is converted to double. Otherwise, if either operand has
type float, the other operand is converted to float. Otherwise, the
integral promotions are performed on both operands. Then the
following rules are applied: If either operand has type unsigned long
int, the other operand is converted to unsigned long int.
Otherwise, if one operand has type long int and the other has type
unsigned int, if a long int can represent all values of an unsigned
int, the operand of type unsigned int is converted to long int ; if a
long int cannot represent all the values of an unsigned int, both
operands are converted to unsigned long int. Otherwise, if either
operand has type long int, the other operand is converted to long int.
Otherwise, if either operand has type unsigned int, the other
operand is converted to unsigned int. Otherwise, both operands have
type int.

The values of operands and of the results of expressions may be
represented in greater precision and range than that required by the
type; the types are not changed thereby.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.