2013-06-23 6 views
47

K & R не подходит к этому моменту, но использует его. Я попытался посмотреть, как это было бы работать, написав пример программу, но это не так хорошо:Каковы правила для указателей каста в C?

#include <stdio.h> 
int bleh (int *); 

int main(){ 
    char c = '5'; 
    char *d = &c; 

    bleh((int *)d); 
    return 0; 
} 

int bleh(int *n){ 
    printf("%d bleh\n", *n); 
    return *n; 
} 

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

+1

INT имеет больший размер, чем полукокс, так это чтение вне пространства «5» символа. Попробуйте сделать то же самое, используя меньший тип данных (int c, printf "% c") – SheetJS

+1

Значение '* n' будет' int', которое должно быть 4 байта. '* n' указывает на локальную переменную' c' в 'main()'. Это означает, что вы будете записывать значение '' c'' и все три байта следуют за ним в памяти. (Мое предположение - значение 'd'.) Вы можете проверить это, записав номер в шестнадцатеричном формате - две цифры должны быть одинаковыми каждый раз. – millimoose

+0

'' 5'' - вы думаете, что это выглядит как int, поскольку оно кажется числом, но это всего лишь символ, который представляет цифру. 5. – mah

ответ

2

У вас есть указатель на char. Так как ваша система знает, на этом адресе памяти есть значение char на sizeof(char). Когда вы произведете его до int*, вы будете работать с данными sizeof(int), поэтому вы будете печатать свой символ и некоторый мусор памяти после него как целое число.

25
char c = '5' 

char (1 байт) выделяется в стеке по адресу 0x12345678.

char *d = &c; 

Вы получаете адрес c и хранить его в d, так d = 0x12345678.

int *e = (int*)d; 

Вы заставить компилятор считать, что 0x12345678 точки к int, но ИНТ не только один байт (sizeof(char) != sizeof(int)). Это может быть 4 или 8 байтов в соответствии с архитектурой или даже другими значениями.

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

+2

Другие последовательные байты не являются мусором, а значением 'd', то есть' 0x12345678' в вашем примере. – Kane

+0

'd' недостаточно велик, чтобы держать' 0x12345678' –

+0

@APerson Почему? – YoYoYonnY

2

Я подозреваю, что вам нужен более общий ответ:

Там нет правил на указатели литья в C! Язык позволяет вам указывать любой указатель на любой другой указатель без комментариев.

Но дело в том, что: Нет никакого преобразования данных или что-то сделано! Его единственная ответственность за то, что система не ошибочно интерпретирует данные после трансляции, что, как правило, будет иметь место, что приведет к ошибке времени выполнения.

Поэтому, когда вы полностью решаете, что, если данные используются из литого указателя, данные совместимы!

C оптимизирован для работы, поэтому ему не хватает рефлексивности времени указателей/ссылок. Но у вас есть цена - вы, как программист, должны лучше заботиться о том, что вы делаете. Вы должны знать о вашей собственной личности, если то, что вы хотите сделать, это «правовой»

+7

Существуют правила о указателях на литье, некоторые из которых приведены в разделе 6.3.2.3 стандарта C 2011. Среди прочего, указатели на объекты могут быть переданы другим указателям на объекты и, если они будут преобразованы обратно, будут сравниваться с оригиналом. Указатели на функции могут быть переданы другим указателям на функции и, если они будут преобразованы обратно, будут сравниваться с равными. Преобразование указателей на функции в указатели на объекты приводит к неопределенному поведению.Указатели на объекты могут быть преобразованы в указатели на символы и использованы для доступа к байтам объекта. –

+0

Разрешено преобразовывать указатели на функции указателей на объекты. «Указатель на функцию может быть перенесен на указатель на объект или на void, позволяя проверять или изменять функцию (например, отладчиком)», J.5.7 – aqjune

+0

@aqjune Вы цитируете _popular extension_ на C, который по определению не является стандартным C. Он информативен только. – pipe

10

Кастинг указатели обычно недействительны в С. Есть несколько причин:

  1. центровки. Возможно, что из-за соображений выравнивания тип указателя назначения не может представить значение типа указателя источника. Например, если int * были по сути 4-байт выровнены, отливка char * до int * потеряла бы нижние биты.

  2. Aliasing. В общем случае запрещается доступ к объекту, за исключением того, что для объекта соответствует значение lvalue. Есть некоторые исключения, но если вы их не понимаете очень хорошо, вы не хотите этого делать. Обратите внимание, что сглаживание - это только проблема, если вы действительно разыщите указатель (примените к нему * или ->) или передайте его функции, которая будет разыменовывать ее).

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

  1. Когда точки назначения типа указатель на символьный тип. Гарантируется, что указатели на типы символов могут представлять любой указатель на любой тип и при необходимости возвращать его обратно к исходному типу. Указатель на void (void *) точно такой же, как указатель на тип символа, за исключением того, что вам не разрешено разыгрывать его или делать арифметику на нем, и он автоматически преобразуется в другие типы указателей и из них без необходимости приведения, поэтому указатели как правило, предпочтительнее для указателей на типы символов для этой цели.

  2. Когда тип указателя адресата является указателем на тип структуры, чьи члены точно соответствуют начальным членам типа структуры с указанием направления. Это полезно для различных объектно-ориентированных методов программирования на C.

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

+2

Можете ли вы ссылаться на официальный документ с этими неясными случаями? – Eric

+0

Я видел код в нескольких местах, который берет char * и переводит его в какой-то другой указатель, скажем, int. Например, потоковые значения RGB с камеры или байты сети. Означает ли ваша ссылка, что этот код недействителен? Согласование данных, достаточных для правильного ввода кода, или это просто, что наши общие компиляторы снисходительны к этому использованию? –

+1

@EvanBenn: Возможно. Если буфер получен с помощью 'malloc', и вы храните в нем данные через« fread »или аналогичные, то до тех пор, пока смещения соответствующим образом выровнены (в общем, это может быть трудно определить, но это, безусловно, re кратно размеру типа), он должен соответствовать преобразованию в соответствующий тип указателя и получить доступ к данным в этом типе. Но если вы работаете с буфером, фактическим типом которого является 'char [N]' или что-то еще, это неверно. –

89

Когда вы думаете о указателях, это помогает рисует диаграммы. Указатель - это стрелка, указывающая на адрес в памяти, с меткой, указывающей тип значения. Адрес указывает, где искать, и тип указывает, что делать. Наведение указателя изменяет метку на стрелке, но не там, где указана стрелка.

d в main является указателем на c, который имеет тип char. A char - один байт памяти, поэтому, когда d разыменовывается, вы получаете значение в этом байте памяти. На приведенной ниже диаграмме каждая ячейка представляет один байт.

-+----+----+----+----+----+----+- 
| | c | | | | | 
-+----+----+----+----+----+----+- 
     ^~~~ 
     | char 
     d 

Когда вы приводите d к int*, вы говорите, что d действительно указывает на int значение. В большинстве систем сегодня int занимает 4 байта.

-+----+----+----+----+----+----+- 
| | c | ?₁ | ?₂ | ?₃ | | 
-+----+----+----+----+----+----+- 
     ^~~~~~~~~~~~~~~~~~~ 
     | int 
     (int*)d 

Когда вы разыменования (int*)d, вы получаете значение, которое определяется из этих четырех байтов памяти. Значение, которое вы получаете, зависит от того, что находится в этих ячейках с пометкой ?, а также о том, как int представлен в памяти.

ПК является little-endian, что означает, что значение из int вычисляется следующим образом (при условии, что оно охватывает 4 байта): * ((int*)d) == c + ?₁ * 2⁸ + ?₂ * 2¹⁶ + ?₃ * 2²⁴. Таким образом, вы увидите, что в то время как значение является мусором, если вы печатаете в шестнадцатеричном формате (printf("%x\n", *n)), последние две цифры всегда будут 35 (это значение символа '5').

Некоторые другие системы являются большими и упорядочивают байты в другом направлении: * ((int*)d) == c * 2²⁴ + ?₁ * 2¹⁶ + ?₂ * 2⁸ + ?₃. В этих системах вы обнаружите, что значение всегда начинает с 35 при печати в шестнадцатеричном формате. Некоторые системы имеют размер int, который отличается от 4 байтов. Редкие несколько систем упорядочивают int по-разному, но вы вряд ли встретите их.

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

В некоторых системах значение int должно храниться в адресе, кратном 4 (или 2 или 8). Это называется требованием alignment. В зависимости от того, правильно ли выровнен адрес c, программа может выйти из строя.

В отличие от вашей программы, вот что произойдет, если у вас есть значение int и возьмите указатель на него.

int x = 42; 
int *p = &x; 
-+----+----+----+----+----+----+- 
| |   x   | | 
-+----+----+----+----+----+----+- 
     ^~~~~~~~~~~~~~~~~~~ 
     | int 
     p 

Указатель p указывает на int значение. Метка на стрелке правильно описывает, что находится в ячейке памяти, поэтому нет сюрпризов при разыменовании.

+1

Хорошее описание. Я хотел бы указать/обсудить, что на большинстве компьютеров может быть правдой, что int является 32-битным значением, но для других встроенных инженеров int * обычно * 16-бит, и он показывает, насколько полезен и, возможно, важный это использовать uint16_t, uint32_t, int32_t и т. д. Не пытайтесь быть умной задницей, пожалуйста, не обижайтесь. :) – DiBosco

+0

«... последние две цифры всегда будут 35 (это значение символа« 5 »)». Зачем? –

1

Значение мусора связано с тем, что вы вызывали функцию bleh() перед ее объявлением.

В случае с ++ вы получите ошибку компиляции, но в C, компилятор предполагает, что тип возвращаемого значения функции INT, в то время как ваша функция возвращая указатель на целое число.

Смотреть это для получения дополнительной информации: http://www.geeksforgeeks.org/g-fact-95/

+1

Функция объявляется перед вызовом. – ad3angel1s