Перейти к содержанию
    

Приведение типов в Си

Доброго всем дня!

наткнулся на неожиданное поведение компилятора MCC18 (для PIC18), по нему значится как "ANSI '89 compatibility"

Привожу тип и получаю разные результаты в зависимости...:

signed char x = 0x88;

unsigned short y = x; // y=0xFF88

unsigned short y = (unsigned short)x; // y=0xFF88

unsigned short y = (unsigned char)x; // y=0x0088

Ранее этот же кусочек кода работал в GCC/ARM, IAR AVR и всегда было

unsigned short y = x; // y=0x0088

Я как человек старой закалки понимаю, что нужно было явно приводить тип, и даже самым надежным будет приведение через указатель в память, типа такого:

unsigned short y = *(unsigned short *)&x; // y=0x0088

Но все таки, даже если делать просто приведение по умолчанию как y=x  - что за результат-то такой 0xFF88? И почему приведение (unsigned char) делает как мне нужно, а (unsigned short) - опять странное 0xFF88. Это какая то особенность старого ANSI '89? как будто не логично это...

Изменено пользователем kan35

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

8 minutes ago, kan35 said:

что за результат-то такой 0xFF88

так выглядят "в сыром виде" отрицательные числа, ведь у вас x - char со знаком

signed char x = 0x88;

видимо тут есть проблемы с приведением типов самого компилятора, очень очень давно не работал с MCC18

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

а разве знаковое число не двоично дополненное, 0x88 это же -120 то есть если даже знак продублируется, то должно получиться 0xFF78, но выходит 0xFF88...

Получаются довольно смешные конструкции:

... = ((unsigned short)((unsigned char)x)) << 1;

это если я хочу привести к 16 битному виду и сдвинуть на 1 разряд. То есть два раза подряд привожу тип, это же не должно быть так...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ваши мысли немного "сумбурно" сфомулированы, это сложно комментировать. Хотите четкий ответ, сфотмулируйте четкий вопрос. :dance2:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Мой вопрос сформулирован в первом сообщении предельно понятно и даже с примерами. Нужен эксперт, кто догадывается почему код, который я привел работает именно так а не иначе. Пока я не понимаю логики.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В общем случае, неявные приведения знаковых к беззнаковым - не рекомендованы, и компиляторы, как правило, выдают предупреждения о таких операциях.

 

Снимок экрана 2023-07-15 145512.png

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 часа назад, kan35 сказал:

Привожу тип и получаю разные результаты в зависимости...:

signed char x = 0x88;

unsigned short y = x; // y=0xFF88

unsigned short y = (unsigned short)x; // y=0xFF88

unsigned short y = (unsigned char)x; // y=0x0088

Компилятор сделал всё правильно.

2 часа назад, kan35 сказал:

Но все таки, даже если делать просто приведение по умолчанию как y=x  - что за результат-то такой 0xFF88? И почему приведение (unsigned char) делает как мне нужно, а (unsigned short) - опять странное 0xFF88.

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

Список преобразований, отсортированный по дистанции удалённости в порядке от наибольшей дистанции к наименьшей:

1. Преобразование целочисленное <-> плавающее.

2. Преобразование размера.

3. Преобразование знака.

Таким образом, если приводите знаковый signed char к беззнаковому unsigned short, то в соответствии с правилами си, будет 2 шага преобразований:

1шаг: signed char -> signed short

2шаг: signed short -> unsigned short

Поэтому ваше приведение (unsigned short)x и даёт в результате двух преобразований = 0xFF80u;

а (unsigned char)x - преобразует 0x88 в беззнаковый тип, а при последующем присваивании производится ещё одно приведение - преобразование размера unsigned char -> unsigned short: 0x88u -> 0x0088u

Компилятор всё сделал правильно.

47 минут назад, EdgeAligned сказал:

В общем случае, неявные приведения знаковых к беззнаковым - не рекомендованы

Людям, слабо знающим язык, вообще многое не рекомендовано. :wink:  Учите язык лучше и сможете забить на эти детские страшилки  :unknw:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Да, особенно когда выучите С++, поймете, что за неявные преобразования... кароче понятно да

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, kan35 сказал:

неожиданное поведение

Вы забываете о целочисленных продвижениях и неявных преобразованиях типов. И первое, и второе - очень важные шаги.
 

Цитата

signed char x = 0x88;

0x88 - это тип int в соответствии со стандартом языка. Никакого криминала - при записи из int в signed char происходит усечение разрядности, а то, что при этом получается отрицательное значение для 8-битного signed char - это ответственность программиста.

С точки зрения языка все корректно.
 

Цитата

unsigned short y = x; // y=0xFF88

Опять же, правило продвижения целочисленных типов перед любыми операциями: тип x сначала выполняет повышение до int, в результате чего происходит расширение знака - т.е. число 0xFF88 в битовом представлении. Я не знаю, сколько бит занимает int в Вашем компиляторе (для примера взял 16 бит), но точно знаю, что стандарт говорит о sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long). И здесь два варианта:

  • если sizeof(short) == sizeof(int), то (согласно стандарту Си) unsigned short может вмещать все возможные битовые представления signed int, значит никакой потери информации не происходит - в y записывается x.
  • если sizeof(short) < sizeof(int), то произойдет усечение разрядности. В общем случае это опасная ситуация, т.к. потенциально теряется часть числа.

Все корректно.
 

Цитата

unsigned short y = (unsigned short)x; // y=0xFF88

То же самое, см. предыдущий пункт. Сначала x неявно продвигается до int, затем усекается до unsigned short, результат записывается в unsigned short.
 

Цитата

unsigned short y = (unsigned char)x; // y=0x0088

Надеюсь, объяснять не нужно. Тоже все корректно.
 

1 час назад, EdgeAligned сказал:

В общем случае, неявные приведения знаковых к беззнаковым - не рекомендованы...

Не знаю, кем не рекомендованы, но неявное приведение знаковых к беззнаковым (при том же ранге типа) безопасно, в отличие от обратного преобразования, которое сразу UB.

P.S. И кстати,

3 часа назад, kan35 сказал:

unsigned short y = *(unsigned short *)&x; // y=0x0088

конструкция потенциально опасная, т.к. в ней сразу 2 бага.


P.S.2. И кстати,

Цитата

Ранее этот же кусочек кода работал в GCC/ARM, IAR AVR и всегда было

unsigned short y = x; // y=0x0088

весьма сомнительно, ибо никогда правдой не было:wink:

Либо signed у char не писался, предполагая знаковый, а на самом деле в настройках был unsigned as default.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Всем большое спасибо за ответы! Мне в общем как бы всё понятно, в общем то и с самого начала было ясно, просто не понимаю почему GCC ARM делает

unsigned short y = x; // y=0x0088

а старый компилятор ANSI'89

unsigned short y = x; // y=0xFF88

Говорить, один делает

Quote

С точки зрения языка все корректно. 

, значит и утверждать, что другой делает не всё корректно.

У меня такое предположение, что, наверное, в более новых компиляторах (может и в стандартах это и описано), они учли, что если уж приводишь сразу к unsigned, то не надо втаскивать двоичное дополнение в виде FF, если бы мне это было нужно, я бы делал signed. Ну а с точки зрения суровых ранних компиляторов нужно все делать корректно, приводя тип "постепенно".

 

unsigned short y = *(unsigned short *)&x; // y=0x0088

согласен, тут ошибка, конечно, я собирался написать так:

unsigned short y = *(unsigned char *)&x; // y=0x0088

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

4 минуты назад, kan35 сказал:

просто не понимаю почему GCC ARM делает

unsigned short y = x; // y=0x0088

а старый компилятор ANSI'89

unsigned short y = x; // y=0xFF88

Не было там в GCC ARM такого, и быть не могло, соответственно, дальнейшие рассуждения на тему новых компиляторов не имеют особого смысла:wink:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

4 hours ago, kan35 said:

Я как человек старой закалки понимаю, что нужно было явно приводить тип, и даже самым надежным будет приведение через указатель в память, типа такого:

signed char x = 0x88;
unsigned short y = *(unsigned short *)&x; // y=0x0088

После этого уже можно дальше не анализировать... На большинстве компиляторов кроме значения символа будет прихвачено еще какое-то значение из соседней памяти. Вместо  y=0x0088 может оказаться  y=0x3088

Изменено пользователем GenaSPB

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

@GenaSPB, он уже поправил выше... Но, раз уж так, прихваченный мусор был бы гораздо меньшим из зол.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
К сожалению, ваш контент содержит запрещённые слова. Пожалуйста, отредактируйте контент, чтобы удалить выделенные ниже слова.
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...