Как компиляторы C / C ++ обрабатывают приведение типов между типами с разными значениями? - PullRequest
12 голосов
/ 04 декабря 2008

Как происходит приведение типов без потери данных внутри компилятора?

Например:

 int i = 10;
 UINT k = (UINT) k;

 float fl = 10.123;
 UINT  ufl = (UINT) fl; // data loss here?

 char *p = "Stackoverflow Rocks";
 unsigned char *up = (unsigned char *) p;

Как компилятор обрабатывает этот тип приведения типов? Пример с низким уровнем, показывающий биты, будет высоко оценен.

Ответы [ 4 ]

19 голосов
/ 04 декабря 2008

Ну, во-первых, обратите внимание, что приведение - это явный запрос для преобразования значения одного типа в значение другого типа . Приведение также всегда создает новый объект, который временно возвращается оператором приведения. Однако приведение к ссылочному типу не приведет к созданию нового объекта. Объект, на который ссылается значение, повторно интерпретируется как ссылка другого типа.

Теперь к вашему вопросу. Обратите внимание, что существует два основных типа конверсий:

  • Акции : Этот тип можно рассматривать как приведение к типу от более узкого к более широкому типу. Преобразование из char в int, short в int, float to double - все это акции.
  • Преобразования : они позволяют приводить от long к int, int к unsigned int и так далее. Они могут в принципе вызвать потерю информации. Существуют правила того, что происходит, если вы, например, назначаете -1 типизированному объекту без знака. В некоторых случаях неправильное преобразование может привести к неопределенному поведению. Если вы назначите двойное значение больше, чем то, что может хранить число с плавающей точкой, поведение не будет определено.

Давайте посмотрим на ваши заклинания:

int i = 10; 
unsigned int k = (unsigned int) i; // :1

float fl = 10.123;
unsigned int  ufl = (unsigned int) fl; // :2

char *p = "Stackoverflow Rocks"; 
unsigned char *up = (unsigned char *) p; // :3
  1. Это приведение вызывает преобразование. Потеря данных не происходит, так как 10 гарантированно будет сохранено unsigned int. Если бы целое число было отрицательным, значение в основном обернулось бы вокруг максимального значения целого без знака (см. 4.7 / 2 ).
  2. Значение 10.123 усекается до 10. Здесь оно действительно вызывает потерю информации, очевидно. Поскольку 10 вписывается в беззнаковое целое, поведение определяется.
  3. Это на самом деле требует большего внимания. Во-первых, не рекомендуется преобразовывать строковый литерал в char*. Но давайте проигнорируем это здесь. (см. здесь ). Что еще более важно, что произойдет, если вы приведете к типу без знака? На самом деле, результат этого не определен для 5.2.10 / 7 (обратите внимание, что семантика этого приведения такая же, как при использовании reinterpret_cast в этом случае, так как это единственный C ++ -тиль, способный это сделать) :

Указатель на объект может быть явно преобразован в указатель на объект другого типа. За исключением того, что преобразование значения типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 являются типами объектов и где требования к выравниванию T2 не являются более строгими, чем требования к T1) и обратно к его исходному типу, дает исходное значение указателя, результат такого преобразования указателя не определен.

Таким образом, вы можете безопасно использовать указатель только после того, как снова вернетесь к char *.

8 голосов
/ 04 декабря 2008

В вашем примере два приведения в стиле C - это разные типы приведения. В C ++ вы обычно пишете их

unsigned int uf1 = static_cast<unsigned int>(fl);

и

unsigned char* up = reinterpret_cast<unsigned char*>(p);

Первый выполняет арифметическое приведение, которое усекает число с плавающей запятой, поэтому происходит потеря данных.

Второй не вносит изменений в данные - он просто указывает компилятору обрабатывать указатель как другой тип. С этим типом заклинания нужно быть осторожным: это может быть очень опасно.

5 голосов
/ 04 декабря 2008

«Тип» в C и C ++ - это свойство, назначаемое переменным, когда они обрабатываются в компиляторе. Это свойство больше не существует во время выполнения, за исключением виртуальных функций / RTTI в C ++.

Компилятор использует тип переменных для определения многих вещей. Например, при присваивании числа с плавающей точкой int, он будет знать, что ему нужно преобразовать. Оба типа, вероятно, 32-битные, но имеют разные значения. Вероятно, у процессора есть инструкция, но в противном случае компилятор узнает, что нужно вызвать функцию преобразования. То есть & __stack[4] = float_to_int_bits(& __stack[0])

Преобразование из char * в unsigned char * даже простое. Это просто другой лейбл. На уровне битов p и вверх идентичны. Компилятору просто нужно помнить, что * p требует расширения знака, а * up - нет.

1 голос
/ 04 декабря 2008

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

Приведение даже не должно приводить к пригодному значению. Что-то вроде char * cp; float * fp; cp = malloc(100); fp = (float *)(cp + 1); почти наверняка приведет к смещенному указателю на float, что приведет к сбою программы в некоторых системах, если программа попытается его использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...