Как int преобразуется в char и как char преобразуется в int?

В следующем примере выводится битовое представление byte со всеми единицами:

#include <stdio.h>
int main (void)
{
  char c = 255;
  char z;
  for (int i = 7; i >= 0; i--) {
    z = 1 << i;
    if ((z & c) == z) printf("1"); else printf("0");
  }
  printf("\n");
  return 0;
}

Выход 11111111

Теперь мы меняем char c на int c, чтобы пример стал таким:

#include <stdio.h>
int main (void)
{
  int c = 255;
  char z;
  for (int i = 7; i >= 0; i--) {
    z = 1 << i;
    if ((z & c) == z) printf("1"); else printf("0");
  }
  printf("\n");
  return 0;
}

Теперь выход 01111111.

Почему выход другой?

ОБНОВЛЕНИЕ

Скомпилируйте следующее test.c:

#include <stdio.h>
int main(void)
{
  char c=-1;
  printf("%c",c);
  return 0;
}

$ gcc test.c
$ ./a.out | od -b
0000000 377
0000001

Вывод 377, что означает, что glibc противоречит gcc, потому что подписанный символ автоматически преобразуется в беззнаковый. Почему такие сложности? Разумно иметь char без знака по умолчанию. Есть какая-то конкретная причина, почему нет?


person Igor Liferenko    schedule 08.11.2016    source источник
comment
Если вам нужна определенная разрядность, используйте типы фиксированной разрядности из stdint.h. И при смещении, как правило, лучше использовать беззнаковые типы, потому что определенные сдвиги/значения для целых чисел со знаком определяются реализацией или даже вызывают неопределенное поведение.   -  person too honest for this site    schedule 08.11.2016


Ответы (3)


  • Первая проблема здесь — тип char. Этот тип никогда не следует использовать для хранения целочисленных значений, поскольку он имеет определяемую реализацией подпись. Это означает, что он может быть как подписанным, так и неподписанным, и вы получите разные результаты на разных компиляторах. Если char не имеет знака в данном компиляторе, то этот код будет вести себя так, как вы ожидали.

    Но если char подписано, char c = 255; приведет к слишком большому значению. Затем значение 255 будет преобразовано в число со знаком некоторым специфичным для компилятора способом. Обычно путем перевода значения необработанных данных в эквивалент дополнения до двух.

    Хорошие компиляторы, такие как GCC, предупредят об этом: "переполнение при неявном преобразовании констант".

    Устраните эту ошибку, никогда не используя char для хранения целых чисел. Вместо этого используйте uint8_t.

  • Та же проблема возникает, когда вы пытаетесь сохранить 1 << 7 внутри типа char, подписанного вашим компилятором. Когда это произойдет, z станет отрицательным значением (-128).

  • В выражении z & c оба операнда являются целочисленными, преобразованными в тип int. Это происходит в большинстве выражений C всякий раз, когда вы используете небольшие целочисленные типы, такие как char.

    Оператор & не заботится о том, подписаны ли операнды или нет, он будет выполнять побитовое И над значениями "необработанных данных" переменных. Когда c является знаком char и имеет необработанное значение 0xFF, вы получите отрицательный результат с установленным битом знака. Значение -1 на компьютерах с дополнением до двух.

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

Когда вы переключаете тип на int, значение 255 помещается внутри c без преобразования в отрицательное значение. Результатом операции & также будет int, и бит знака этого int никогда не будет установлен, в отличие от случая char.

При выполнении -128 & 255 результатом будет 128 (0x80). Это положительное целое число. Однако z является отрицательным целым числом со значением -128. Он будет повышен до int оператором ==, но знак будет сохранен. Поскольку 128 не равно -128, старший бит будет напечатан как ноль.

Вы получите тот же результат, если переключите char на uint8_t.

person Lundin    schedule 08.11.2016
comment
255 & -1 следует заменить на 128 & -1 - person Igor Liferenko; 09.11.2016
comment
см. ОБНОВЛЕНИЕ в ОП - person Igor Liferenko; 09.11.2016
comment
Почему они украли char, а не создали int8_t? Не лучше ли, когда вещи называют своими именами? char предназначался для хранения символьныхкодов персонажей (т. е. количества без знака). - person Igor Liferenko; 09.11.2016
comment
@IgorLiferenko 255 и -1 следует изменить на 128 и -1 Нет, это правильно. - person Lundin; 09.11.2016
comment
@IgorLiferenko смотрите ОБНОВЛЕНИЕ в ОП. Обновлять нет смысла. Каким вы ожидали увидеть символ -1? Этот код будет печатать только ерунду, если что, независимо от подписи char. - person Lundin; 09.11.2016
comment
Не лучше ли, когда вещи называют своими именами? char предназначался для хранения кодов символов (т. е. количества без знака). Язык C не был разработан рационально. Многие решения при проектировании и стандартизации языка были плохими. Проблема заключалась в том, что на момент стандартизации некоторые компиляторы реализовали char так, чтобы он вел себя как остальные целочисленные типы (со знаком по умолчанию), другие компиляторы реализовали его как символьный тип (без знака, поскольку таблицы символов не имеют отрицательных индексов). Так что они разрешили обе формы. - person Lundin; 09.11.2016
comment
Многие проблемы с языком C на самом деле были вызваны ранней стандартизацией ISO. При разработке стандарт ISO не позволяет отдавать преимущества одним технологиям перед другими. Вот почему C полон контрпродуктивного мусора, такого как определяемая реализацией подпись или не предполагать, что байт равен 8 битам. - person Lundin; 09.11.2016
comment
Нет, это правильно Тогда результат будет 128 (0x80) нужно изменить на результат будет 255 (0xFF) - person Igor Liferenko; 10.11.2016
comment
@IgorLiferenko А, теперь я вижу проблему. На самом деле это должно быть 255 & -128. Я отредактирую. - person Lundin; 10.11.2016

для char в int вы должны определить char как unsigned, потому что по умолчанию char или любой другой тип рассматривается как singed.

 int main (void)
 {
 int c = 255;
 unsigned char z;
 int i;
 for (i = 7; i >= 0; i--) {
 z = 1 << i;
 if ((z & c) == z) printf("1"); else printf("0");
 }
 printf("\n");
 return 0;
 }
person Sumit Gemini    schedule 08.11.2016
comment
Нет, char имеет подписанность, определяемую реализацией. Он может быть как подписанным, так и беззнаковым, это зависит от компилятора. - person Lundin; 08.11.2016
comment
@Lundin спасибо, я не знал об этом, потому что я использую только компилятор gcc, а в gcc он упоминается. По умолчанию char подписывается - person Sumit Gemini; 08.11.2016

(изменить, чтобы уточнить "подписано по умолчанию")

В первом листинге (z == c) проверяет два char ; однако во втором листинге (z == c) проверяет один char и один int.

Для выполнения операций & и == между char и int компилятор расширяет char до размера int. .

Что касается бита 7 (8-й):

Если ваш компилятор считает char беззнаковым по умолчанию, условие

(((int)(128) & (int)255) == (int)128)

будет отображаться true, и будет напечатано 1. Однако в вашем случае результат ложный, и отображается 0.

Причина, скорее всего, в том, что ваш компилятор считает char подписанным (например, gcc по умолчанию). В этом случае char, установленный в 1 << 7, на самом деле -128, а в int (не менее двух байтов) 255 является положительным.

(char)-128, расширенное до int, равно (int)-128, поэтому условие

if ((z & c) == z) 

читает

if (((int)(-128) & (int)255) == (int)-128)

что неверно в данном случае.

person Breaking not so bad    schedule 08.11.2016
comment
char по умолчанию не подписано Напротив: подписанность по умолчанию определяется реализацией. И преобразование слишком большого значения в целое число со знаком также определяется реализацией. Небольшой недостаток: все операнды с рангом ниже int преобразуются в int. Это не относится к самому int. - person too honest for this site; 08.11.2016