Разделение этого указателя дает мне -46, но я не уверен, почему
Это программа, в которой я запускал:
#include <stdio.h>
int main()
{
int y = 1234;
char *p = &y;
int *j = &y;
printf("%d " , *p);
printf("%d" , *j);
}
Я немного смущен о выходе. Я вижу:
-46 1234
Я написал эту программу в качестве эксперимента и не был уверен, что она собирается выводить. Я ожидал, возможно, один байт от y
.
Что здесь происходит "за кадром"? Как разыменование p
дает мне -46?
Обновление
Как указано на другом, мне пришлось делать явное кастинг, чтобы не вызывать UB. Я не меняю эту строку на char *p = &y to char *p = (char *)&y
, чтобы я не признал недействительным ниже ответов.
Ответы
Ответ 1
Если у вас есть что-то вроде
int x = 1234 ;
int *p = &x;
Если вы указатель разворота p
, тогда он будет корректно считывать целочисленные байты. Потому что вы указали его указателем на int
. Он будет знать, сколько байтов считывается оператором sizeof()
. Обычно размер int
равен 4 bytes
(для 32/64-разрядных платформ), но он зависит от машины, поэтому он будет использовать оператор sizeof()
, чтобы знать правильный размер и будет читать так. Теперь для вашего кода
int y = 1234;
char *p = &y;
int *j = &y;
Теперь pointer p
указывает на y
, но мы объявили его указателем на char
, поэтому он будет читать только один байт или любой из байтов char.
1234
в двоичном формате будет представлен как
& ЕПРС; & ЕПРС; & ЕПРС; & ЕПРС; & ЕПРС; & ЕПРС; & ЕПРС; & ЕПРС; 00000000 00000000 00000100 11010010
Теперь, если ваша машина немного ориентирована, она сохранит байты, обращая их.
& emsp; & emsp; & emsp; & emsp; & emsp; & emsp; & emsp; & emsp; 11010010 00000100 00000000 00000000
11010010
находится в address 00
Hypothetical address
, 00000100
находится в address 01
и так далее.
BE: 00 01 02 03
+----+----+----+----+
y: | 00 | 00 | 04 | d2 |
+----+----+----+----+
LE: 00 01 02 03
+----+----+----+----+
y: | d2 | 04 | 00 | 00 |
+----+----+----+----+
(In Hexadecimal)
Итак, если вы разыскиваете pointer p
, он будет читать только первый байт, и вывод будет (-46
в случае signed char
и 210
в случае unsigned char
, согласно стандарту C, подпись plain char - это "реализация определена" ), поскольку чтение байта будет 11010010
(потому что мы указали signed char
(в данном случае это signed char
).
На вашем ПК отрицательные числа представлены в виде 2 дополнений, поэтому most-significant bit
является битом знака. Первый бит 1
обозначает знак. 11010010 = –128 + 64 + 16 + 2 = –46
, и если вы разыщите pointer j
, он полностью прочитает все байты int
, поскольку мы объявили его указателем на int
, а вывод будет 1234
А также, если вы объявите указатель j как int *j
, тогда *j
будет читать sizeof(int)
здесь 4 байта (зависит от машины). То же самое происходит с char
или любым другим типом данных, указатель, на который они указали, будет читать столько байтов, размер которых равен, char
имеет 1 байт.
Как указано другим, вам нужно явно указать на char*
, поскольку char *p = &y;
является нарушением ограничения - char *
и int *
не являются совместимыми типами, вместо этого пишите char *p = (char *)&y
.
Ответ 2
В коде написано несколько проблем с кодом.
Прежде всего вы вызываете поведение undefined, пытаясь напечатать числовое представление объекта char
с помощью спецификатора преобразования %d
:
Проект онлайн C 2011, раздел 7.21.6.1, подпункт 9:
Если спецификация преобразования недействительна, поведение undefined.282) Если какой-либо аргумент
не правильный тип для соответствующей спецификации преобразования, поведение
undefined.
Да, объекты типа char
продвигаются до int
при передаче в вариативные функции; printf
является специальным, и если вы хотите, чтобы результат был корректным, то тип аргумента и спецификатор преобразования должны совпадать. Чтобы напечатать числовое значение char
с аргументом %d
или unsigned char
с помощью %u
, %o
или %x
, вы должны использовать модификатор длины hh
как часть спецификации преобразования:
printf( "%hhd ", *p );
Вторая проблема заключается в том, что строка
char *p = &y;
- нарушение ограничений - char *
и int *
не являются совместимыми типами и могут иметь разные размеры и/или представления 2. Таким образом, вы должны явно указать источник в целевой тип:
char *p = (char *) &y;
Единственное исключение из этого правила возникает, когда один из операндов void *
; то приведение не требуется.
Сказав все это, я взял ваш код и добавил утилиту, которая сбрасывает адрес и содержимое объектов в программе. Здесь y
, p
и j
выглядят как на моей системе (SLES-10, gcc 4.1.2):
Item Address 00 01 02 03
---- ------- -- -- -- --
y 0x7fff1a7e99cc d2 04 00 00 ....
p 0x7fff1a7e99c0 cc 99 7e 1a ..~.
0x7fff1a7e99c4 ff 7f 00 00 ....
j 0x7fff1a7e99b8 cc 99 7e 1a ..~.
0x7fff1a7e99bc ff 7f 00 00 ....
Я нахожусь в x86-системе, которая является малоконтинентальной, поэтому она хранит многобайтные объекты, начиная с младшего значащего байта на самом нижнем адресе:
BE: A A+1 A+2 A+3
+----+----+----+----+
y: | 00 | 00 | 04 | d2 |
+----+----+----+----+
LE: A+3 A+2 A+1 A
В малоинтенсивной системе адресный байт является наименее значимым байтом, который в этом случае 0xd2
(210
unsigned, -46
подписан).
В двух словах вы печатаете подписанное десятичное представление этого одиночного байта.
Что касается более широкого вопроса, то тип выражения *p
равен char
, а тип выражения *j
равен int
; компилятор просто идет по типу выражения. Компилятор отслеживает все объекты, выражения и типы, поскольку он переводит ваш исходный код в машинный код. Поэтому, когда он видит выражение *j
, он знает, что он имеет дело с целым значением и соответствующим образом генерирует машинный код. Когда он видит выражение *p
, он знает, что он имеет дело с значением char
.
- По общему признанию, почти все современные настольные системы, которые, как я знаю, используют одни и те же представления для всех типов указателей, но для более нечетких встроенных или специализированных платформ, это может быть неверно.
- & раздел; 6.2.5, подпункт 28.
Ответ 3
(Обратите внимание, что этот ответ относится к исходной форме вопроса, в котором спрашивается, как программа знает, сколько байтов читать и т.д. Я поддерживаю это на этой основе, несмотря на то, что коврик был вытащен из-под нее.)
Указатель ссылается на местоположение в памяти, которое содержит конкретный объект, и должен быть увеличен/уменьшен/проиндексирован с определенным размером шага, отражающим sizeof
заостренный тип.
Наблюдаемое значение самого указателя (например, через std::cout << ptr
) не должно отражать какой-либо узнаваемый физический адрес, и ++ptr
не нужно увеличивать указанное значение на 1, sizeof(*ptr)
или что-либо еще. Указатель - это всего лишь дескриптор объекта с представлением бит, определенным реализацией. Это представление не имеет и не должно иметь значения для пользователей. Единственное, для чего пользователи должны использовать указатель, - это... ну, указать на вещи. Обсуждение его адреса является непереносимым и полезно только при отладке.
В любом случае, компилятор знает, сколько байтов для чтения/записи, потому что указатель набран, и этот тип имеет определенный sizeof
, представление и сопоставление с физическими адресами. Таким образом, на основе этого типа операции с ptr
будут скомпилированы в соответствующие инструкции для вычисления реального аппаратного адреса (который опять же не должен соответствовать наблюдаемому значению ptr
), прочитайте правильный номер sizeof
памяти "байты", добавить/вычесть правильное количество байтов, чтобы он указывал на следующий объект и т.д.