Разделение этого указателя дает мне -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 памяти "байты", добавить/вычесть правильное количество байтов, чтобы он указывал на следующий объект и т.д.