C type casting

User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

C type casting

Post by perasperaadastra »

Подскажите пожалуйста, как компилятор отнесется к следующему куску кода?

Code: Select all

uint8_t varX = 0;
uint16_t varY = 4 * (99225000 + (2*varX-1)*100000) / 32768;
Конкретно, меня интересует то, как компилятор посчитает (2*varX-1).
varX и varY обе беззнаковые переменные. Если не делать кастинг вручную, сочтет ли компилятор, что (2*varX-1) это тоже беззнаковый результат? Или же он автоматически сделает промежуточный кастинг? В первом случае, как я понимаю, 2*0-1 = 255, а во втором, 2*0-1= -1

Если я хочу убедиться, что всё будет нормально, правильно ли я понимаю, что нужно сделать так:

Code: Select all

(2*(int8_t)varX-1)
PS Наверно пример неудачный. Даже если 2*0-1 = 255, конечный результат все равно будет правильным из-за того, что добавляется число, которое сделает результат положительным...
User avatar
Flash-04
Уже с Приветом
Posts: 63430
Joined: 03 Nov 2004 05:31
Location: RU -> Toronto, ON

Re: C type casting

Post by Flash-04 »

Если склероз мне не изменяет, компилятор вначале приводит все переменные к одному типу (самому "широкому") потом считает, а потом впихивает в ожидаемый output.
Not everyone believes what I believe but my beliefs do not require them to.
User avatar
Flash-04
Уже с Приветом
Posts: 63430
Joined: 03 Nov 2004 05:31
Location: RU -> Toronto, ON

Re: C type casting

Post by Flash-04 »

Кстати тип констант зависит от платформы. Раньше это были 16 битовые числа (а не 8!),потом стали 32.
Not everyone believes what I believe but my beliefs do not require them to.
User avatar
DVK
Уже с Приветом
Posts: 2250
Joined: 15 Aug 2003 15:02

Re: C type casting

Post by DVK »

Flash-04 wrote: 23 May 2017 14:36 Если склероз мне не изменяет, компилятор вначале приводит все переменные к одному типу (самому "широкому") потом считает, а потом впихивает в ожидаемый output.
Какой тип "шире": signed char (от -128 до 127) или unsigned char (от 0 до 255)?
:D
"Главная проблема цитат в сети Интернет в том, что люди сразу верят в их подлинность" В.И.Ленин
User avatar
DVK
Уже с Приветом
Posts: 2250
Joined: 15 Aug 2003 15:02

Re: C type casting

Post by DVK »

perasperaadastra wrote: 23 May 2017 04:57 Подскажите пожалуйста, как компилятор отнесется к следующему куску кода?

Code: Select all

uint8_t varX = 0;
uint16_t varY = 4 * (99225000 + (2*varX-1)*100000) / 32768;
Конкретно, меня интересует то, как компилятор посчитает (2*varX-1).
varX и varY обе беззнаковые переменные. Если не делать кастинг вручную, сочтет ли компилятор, что (2*varX-1) это тоже беззнаковый результат? Или же он автоматически сделает промежуточный кастинг? В первом случае, как я понимаю, 2*0-1 = 255, а во втором, 2*0-1= -1

Если я хочу убедиться, что всё будет нормально, правильно ли я понимаю, что нужно сделать так:

Code: Select all

(2*(int8_t)varX-1)
PS Наверно пример неудачный. Даже если 2*0-1 = 255, конечный результат все равно будет правильным из-за того, что добавляется число, которое сделает результат положительным...
::std::is_unsigned<decltype((2*0U-1))>::value выдаёт true, а ::std::is_unsigned<decltype((2*0-1))>::value - false
"Главная проблема цитат в сети Интернет в том, что люди сразу верят в их подлинность" В.И.Ленин
User avatar
DVK
Уже с Приветом
Posts: 2250
Joined: 15 Aug 2003 15:02

Re: C type casting

Post by DVK »

Flash-04 wrote: 23 May 2017 14:37 Кстати тип констант зависит от платформы. Раньше это были 16 битовые числа (а не 8!),потом стали 32.
Для целочисленных литер (а не констант(!), как многие их называют), настоятельно советую использовать суффиксы. Облегчите жизнь себе сильно
"Главная проблема цитат в сети Интернет в том, что люди сразу верят в их подлинность" В.И.Ленин
User avatar
AndreyT
Уже с Приветом
Posts: 3003
Joined: 14 Apr 2004 01:11
Location: SFBA (было: Минск, Беларусь)

Re: C type casting

Post by AndreyT »

perasperaadastra wrote: 23 May 2017 04:57 Подскажите пожалуйста, как компилятор отнесется к следующему куску кода?

Code: Select all

uint8_t varX = 0;
uint16_t varY = 4 * (99225000 + (2*varX-1)*100000) / 32768;
Конкретно, меня интересует то, как компилятор посчитает (2*varX-1).
Переменная varX имеет "маленький" тип uint8_t. Это значит, что еще до начала каких-либо вычислений она будет безусловно подвергнута integral promotions, т.е. ее значение будет неявно приведено к типу int. Языки С и С++ никогда не выполняют арифметические вычисления в "маленьких" типах (за редким исключением), а всегда сначала неявно приводят "маленькие" типы к типу int (или, в экзотических случаях, к unsigned int). А константы 2 и 1 и так имеют тип int. То есть подвыражение (2*varX-1) будет вычисляться в домене знакового типа int и его результат будет иметь знаковый тип int.

То же относится и ко всему остальному выражению, если предполагать, что константа 99225000 помещается в тип int на данной платформе. (Если нет, то она получит тип long. Сложение 99225000 + выполнится в домене типа long и остаток выражения вычислится в домене long).

Только в самом конце результат вычисления (типа int или long) будет сконвертирован к типу uint16_t. Такая конверсия определена языком и сводится к приведению по модулю (UINT16_MAX + 1).
perasperaadastra wrote: 23 May 2017 04:57varX и varY обе беззнаковые переменные. Если не делать кастинг вручную, сочтет ли компилятор, что (2*varX-1) это тоже беззнаковый результат?
Нет. Integral promotions неявно приведут малый беззнаковый uint8_t к знаковому типу int. Вычисления будут делаться в знаковом типе и результат будет знаковым.
perasperaadastra wrote: 23 May 2017 04:57Если я хочу убедиться, что всё будет нормально, правильно ли я понимаю, что нужно сделать так: (2*(int8_t)varX-1)
Зависит от того, что имеется в виду под "нормально". Выбор типа int8_t - странен, ибо его диапазон не покрывает диапазон исходного типа uint8_t. Если хочется знаковых вычислений, то лучше уж взять тип int для явного приведения, особенно если учесть, что константы 1 и 2 имеют тип int. Но необходимости в явном приведении нет.

Если же хочется все вычислять в беззнаковом домене, то надо просто сделать так, чтобы все операнды были беззнаковыми

Code: Select all

uint16_t varY = 4u * (99225000u + (2u*varX-1u)*100000u) / 32768u;
Best regards,
Андрей
User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

Re: C type casting

Post by perasperaadastra »

Flash-04 wrote: 23 May 2017 14:36 Если склероз мне не изменяет, компилятор вначале приводит все переменные к одному типу (самому "широкому") потом считает, а потом впихивает в ожидаемый output.
Вот, я тоже подумал, что это логичное поведение, когда мешаются типы разного разного размера. Но если есть последовательность нескольких арифметических действий, можно ли доверять компилятору делать "расширение" типа только тогда, когда без него не обойтись, или же он тупо сразу возьмет все переменные и приведет к самому широкому типу? Вопрос не праздный — у меня 8-битные регистры, и когда я складываю 8-битную переменную с 32-битной, хотелось бы избежать ситуации, когда 8-битная переменная превращается в 32-битную, а потом складываются ненужные старшие байты. Возможно, оптимизатор это ловит, а может быть и нет — хотелось бы знать, что происходит, не заглядывая в ассембли вывод.
DVK wrote: 23 May 2017 16:55
Flash-04 wrote: 23 May 2017 14:37 Кстати тип констант зависит от платформы. Раньше это были 16 битовые числа (а не 8!),потом стали 32.
Для целочисленных литер (а не констант(!), как многие их называют), настоятельно советую использовать суффиксы. Облегчите жизнь себе сильно
Но они какие-то "неопределенные". К примеру, 10u это unsigned int. Но, как я понимаю, определение int разнится от платформы к платформе... Как я могу указать, что 10 это строго 8 бит, а не какие-нибудь 16?

UPD: AndreyT уже ответил, пока я писал эти вопросы. :great:
Last edited by perasperaadastra on 23 May 2017 18:24, edited 1 time in total.
User avatar
perasperaadastra
Уже с Приветом
Posts: 20128
Joined: 21 Feb 2009 22:55
Location: Лох Онтарио

Re: C type casting

Post by perasperaadastra »

AndreyT wrote: 23 May 2017 17:58 Переменная varX имеет "маленький" тип uint8_t. Это значит, что еще до начала каких-либо вычислений она будет безусловно подвергнута integral promotions, т.е. ее значение будет неявно приведено к типу int. Языки С и С++ никогда не выполняют арифметические вычисления в "маленьких" типах (за редким исключением), а всегда сначала неявно приводят "маленькие" типы к типу int (или, в экзотических случаях, к unsigned int). А константы 2 и 1 и так имеют тип int. То есть подвыражение (2*varX-1) будет вычисляться в домене знакового типа int и его результат будет иметь знаковый тип int.
...
Спасибо за подробный ответ! Загвоздка в том, что у меня 8-битный контроллер, поэтому integer promotion имеет неприятные последствия для производительности. Буду читать документацию, как избежать лишние вычисления без использования ассембли. Мне кажется, что avr-gcc должен иметь способность работать с 8-битными типами.

PS "Integer promotion" — знакомые ключевые слова. У меня такое ощущение, что я уже задавал подобный вопрос, и я раньше узнал про integer promotion тоже из вашего ответа. Дежа вю? Или я теряю память? Надеюсь, что не последнее. :)
User avatar
AndreyT
Уже с Приветом
Posts: 3003
Joined: 14 Apr 2004 01:11
Location: SFBA (было: Минск, Беларусь)

Re: C type casting

Post by AndreyT »

DVK wrote: 23 May 2017 16:55 Для целочисленных литер (а не констант(!), как многие их называют), настоятельно советую использовать суффиксы. Облегчите жизнь себе сильно
В терминологии языка С (а речь пока идет именно о С) буквальные значения называется именно константами, а не литералами. В языке С литералами называются только строковые литералы (string literals) и составные литералы (compound literals). Буквальные скалярные значения - это именно константы (constants).

Называть все буквальные значения литералами стали только в С++. Это разделение терминологии С и С++ по-прежнему строго соблюдается.
Last edited by AndreyT on 23 May 2017 18:37, edited 1 time in total.
Best regards,
Андрей
User avatar
AndreyT
Уже с Приветом
Posts: 3003
Joined: 14 Apr 2004 01:11
Location: SFBA (было: Минск, Беларусь)

Re: C type casting

Post by AndreyT »

perasperaadastra wrote: 23 May 2017 18:23Загвоздка в том, что у меня 8-битный контроллер, поэтому integer promotion имеет неприятные последствия для производительности. Буду читать документацию, как избежать лишние вычисления без использования ассембли. Мне кажется, что avr-gcc должен иметь способность работать с 8-битными типами.
С требует чтобы арифметические вычисления производились в типе [signed/unsigned] int или большем. И при этом требует, чтобы диапазон int был как минимум -32767..+32767. Это автоматически означает, что в рамках языка С компилятор будет вынужден выполнять promotions как минимум до 16-битного типа. Разумеется, это концептуальные promotions. Если компилятор видит, что результат будет правильным и без них, то оно имеет право не выполнять эти promotions физически.

А если надежды на такую оптимизацию нет, то чтобы выполнять вычисления в рамках 8-битного типа придется выходить за рамки языка, т.е. пользоваться нестандартными средствами компилятора.
Best regards,
Андрей
User avatar
DVK
Уже с Приветом
Posts: 2250
Joined: 15 Aug 2003 15:02

Re: C type casting

Post by DVK »

AndreyT wrote: 23 May 2017 18:26
DVK wrote: 23 May 2017 16:55 Для целочисленных литер (а не констант(!), как многие их называют), настоятельно советую использовать суффиксы. Облегчите жизнь себе сильно
В терминологии языка С (а речь пока идет именно о С) буквальные значения называется именно константами, а не литералами. В языке С литералами называются только строковые литералы (string literals) и составные литералы (compound literals). Буквальные скалярные значения - это именно константы (constants).

Называть все буквальные значения литералами стали только в С++. Это разделение терминологии С и С++ по-прежнему строго соблюдается.
Good point. Тем не менее, суффиксы все равно советую использовать, что в С, что в С++
"Главная проблема цитат в сети Интернет в том, что люди сразу верят в их подлинность" В.И.Ленин
User avatar
thinker
Уже с Приветом
Posts: 26871
Joined: 29 Aug 2000 09:01

Re: C type casting

Post by thinker »

perasperaadastra wrote: 23 May 2017 18:14Но они какие-то "неопределенные". К примеру, 10u это unsigned int. Но, как я понимаю, определение int разнится от платформы к платформе... Как я могу указать, что 10 это строго 8 бит, а не какие-нибудь 16?
По этой причине у нас integer вообще не использутся. А вместо него основные типы определены так. Потом их легко переопределить под другой компилятор если потребуется.

Code: Select all

typedef unsigned char   u8_t;   // 8 bit unsigned
typedef unsigned short  u16_t;  // 16 bit unsigned
typedef unsigned long   u32_t;  // 32 bit unsigned
typedef signed char     s8_t;   // 8 bit signed
typedef signed short    s16_t;  // 16 bit signed
typedef signed long     s32_t;  // 32 bit signed
All rights reserved, all wrongs revenged.
User avatar
M. Ridcully
Уже с Приветом
Posts: 12017
Joined: 08 Sep 2006 20:07
Location: Силиконка

Re: C type casting

Post by M. Ridcully »

thinker wrote: 23 May 2017 20:18
perasperaadastra wrote: 23 May 2017 18:14Но они какие-то "неопределенные". К примеру, 10u это unsigned int. Но, как я понимаю, определение int разнится от платформы к платформе... Как я могу указать, что 10 это строго 8 бит, а не какие-нибудь 16?
По этой причине у нас integer вообще не использутся. А вместо него основные типы определены так. Потом их легко переопределить под другой компилятор если потребуется.

Code: Select all

typedef unsigned char   u8_t;   // 8 bit unsigned
typedef unsigned short  u16_t;  // 16 bit unsigned
typedef unsigned long   u32_t;  // 32 bit unsigned
typedef signed char     s8_t;   // 8 bit signed
typedef signed short    s16_t;  // 16 bit signed
typedef signed long     s32_t;  // 32 bit signed
Какие-то экзотические компиляторы нужно поддерживать, где stdint.h отсутствует?
Или код старый?
Мир Украине. Свободу России.
User avatar
thinker
Уже с Приветом
Posts: 26871
Joined: 29 Aug 2000 09:01

Re: C type casting

Post by thinker »

M. Ridcully wrote: 23 May 2017 20:39Какие-то экзотические компиляторы нужно поддерживать, где stdint.h отсутствует?
Или код старый?
stdint.h есть, но у нас свои стандарты на типы данных. Насколько я понял это сделано чтобы независеть от компилятора и "make code more portable".
All rights reserved, all wrongs revenged.

Return to “Вопросы и новости IT”