адрес (&) и косвенный адрес (*) в C - PullRequest
0 голосов
/ 01 февраля 2019

У меня есть вопрос об операторах: address-of (&) и indirection (*) в программировании на C.

Если

  • var - это переменная типаint
  • ptr является указателем на int и указывает на var

, тогда каково будет значение ptr?

Указывает ли результат базовый адрес var или целые 4 байта (в моей платформе)?если он указывает только базовый адрес, то почему *ptr оценивает все содержимое var?разве он не должен содержать содержимое только базового адреса var?

Ответы [ 6 ]

0 голосов
/ 02 февраля 2019

Внутреннее значение / представление ptr, как правило, не указано.Спецификация говорит, что только если вы разыменовываете его, значение имеет некоторый смысл.Все, что вы можете сказать об этом, это то, что это некоторая информация, которая позволяет компьютеру найти значение ссылочного типа.

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

Но стандарт ничего не говорито внутреннем представлении фактического значения указателя.

Это задано так, чтобы позволить различным архитектурам максимально эффективно реализовывать адрес значений.

Этопозволяет, например, реализовать 32-битные процессоры Intel указатель как тип данных segment:offset, который, как таковой, не имеет правильной интерпретации как одно число.В непрозрачной архитектуре виртуальной памяти указатель может потребовать указать дисковое устройство, номер блока и смещение в блоке, где хранится указанное значение, и, таким образом, используя дисковое устройство, номер блока и значение смещения могут сделатьТрудно все интерпретировать как число.Можно утверждать, что значение segment:offset можно интерпретировать как число (как это было в старых процессорах 8086, можно было получить линейный адрес, сдвинув влево на 4 бита селектор сегмента, а затем добавив смещение).Но если вы сделаете это в современных архитектурах виртуальной памяти, вы обнаружите, что не существует простого способа вычисления фактического адреса памяти, если вы считаете, что некоторая информация скрыта операционной системой.Селектор segment является только дескриптором, возможно, большого количества скрытой метаинформации ОС (где сегмент расположен в линейном адресном пространстве, насколько он расширяется, если у вас есть разрешения на освобождение указателя в виде данных или исполняемого кода,и т. д.)

Действительно, два разных указателя могут разыменовать одни и те же окончательные данные .Предположим, два разных указателя, которые были сгенерированы разными средствами, состоят из разных селекторов сегментов и одинаковых или разных смещений, но которые после преобразования отображаются на перекрывающиеся сегменты, которые в конечном итоге указывают на одно и то же место физической памяти .В этом случае нет возможности даже сравнивать такие указатели, но когда вы обращаетесь к одному из указанных значений, вы фактически получаете одни и те же данные из обоих указателей.Работа с указателями такого типа может причинить вам боль и вынудить вас быть очень осторожными при выполнении арифметики указателей с такими указателями (прочитайте о far указателях в старых компиляторах Microsoft), но это не является препятствием для реализацииКомпилятор C на такой архитектуре.

0 голосов
/ 01 февраля 2019

Помните, что тип выражения *ptr - int.То есть, учитывая объявления

int var = 5;
int *ptr = &var;

, тогда выполняются следующие отношения:

 ptr == &var       // int * == int *
*ptr ==  var == 5  // int   == int   == int

Да, значение из ptr является адресомпервый байт 1 из var.Однако выражение *ptr относится ко всему значению int, хранящемуся в var.

Указатели - это абстракция адреса памяти с дополнительной семантикой типов, так что операции с указателями работают одинаково для всех типов.Если ptr указывает на 4-байтовый int, тогда *ptr оценивается в 4-байтовое значение int.Если он указывает на 8-байтовый double, то *ptr оценивается в это 8-байтовое значение double.


Где «первый байт» может быть либо старшим байтом (архитектура с прямым порядком байтов), либо младшим байтом (архитектура с прямым порядком байтов),C скрывает различие за абстракцией типа int, поэтому вам не нужно об этом беспокоиться.
0 голосов
/ 01 февраля 2019

Указывает ли результат базовый адрес var или целых 4 байта (в моей платформе)?если он указывает только базовый адрес, то почему * ptr вычисляет все содержимое var?разве он не должен отображать содержимое только базового адреса var?

Как вы уже упоминали, используемая вами платформа имеет 4 размер байта int.
var - это переменная типа int:

    100   101   102   103
var -------------------------
    |     |     |     |     |
    -------------------------
     byte1 byte2 byte3 byte4

where, 100 - 104 are the address of byte1-byte4 respectively.

ptr - это указатель на int и указывает на var.
когда вы делаете int *ptr = &var;, это означает что-то вроде этого:

   ptr              100   101   102   103
   --------     var -------------------------
   | &var |-------->|     |     |     |     |
   --------         -------------------------
                     byte1 byte2 byte3 byte4

Тип ptr равен int *, то есть указатель на целое число.Итак, тип *ptr равен int.Это означает, что когда вы разыменовываете ptr, это дает значение по адресу, на который указывает ptr, а его тип указывает тип значения, которое int в вашем случае.Вот почему *ptr оценивается как целое int, а не только базовый адрес.


Обратите внимание, что если вы сделаете это

char *c_ptr = (char *)&var;

, это изменит интерпретацию адреса var при доступе с использованием c_ptr и *c_ptr будет интерпретироваться как char.Тем не менее, адреса, по которым ptr и c_ptr указывают на одно и то же число.

Проверьте это:

#include <stdio.h>

int main() {
    int var = 50;
    int *i_ptr = &var;
    char *c_ptr = (char *)&var;

    printf ("address of var: %p\n", (void *)&var);
    printf ("i_ptr: %p\n", (void *)i_ptr);
    printf ("i_char: %p\n\n", (void *)c_ptr);
    printf ("value of var: %d\n", var);
    printf ("value of *i_ptr: %d\n", *i_ptr);

    for (size_t i = 0; i < sizeof(int); i++) {
        printf ("Address of byte[%zu]: %p, ", i, (void *)&c_ptr[i]);
        printf ("byte[%zu]: %c\n", i, c_ptr[i]);
    }
    return 0;
}

Вывод на архитектуре с прямым порядком байтов:

address of var: 0x7ffeea3ac9f8
i_ptr: 0x7ffeea3ac9f8          <========\
i_char: 0x7ffeea3ac9f8         <========/ the address pointing to is same

value of var: 50
value of *i_ptr: 50
Address of byte[0]: 0x7ffeea3ac9f8, byte[0]: 2     <========= 50th character of ascii
Address of byte[1]: 0x7ffeea3ac9f9, byte[1]: 
Address of byte[2]: 0x7ffeea3ac9fa, byte[2]: 
Address of byte[3]: 0x7ffeea3ac9fb, byte[3]: 

Вывод на архитектуре с прямым порядком байтов:

address of var: ffbffbd0
i_ptr: ffbffbd0
i_char: ffbffbd0

value of var: 50
value of *i_ptr: 50
Address of byte[0]: ffbffbd0, byte[0]: 
Address of byte[1]: ffbffbd1, byte[1]: 
Address of byte[2]: ffbffbd2, byte[2]: 
Address of byte[3]: ffbffbd3, byte[3]: 2
0 голосов
/ 01 февраля 2019

ptr, будучи int *, указывает на целые int, или, как вы выразились, целые sizeof(int) байтов.

(unsigned char *)ptr указывает на «базовый адрес», как вы выразились.

ptr и (unsigned char *)ptr будут иметь одинаковое числовое значение на всех распространенных архитектурах ЦП, что демонстрирует разницу между указанием на «целое» целое числои указание только на «базовый адрес» полностью зависит от типа указателя.Важно понимать, что две переменные разных типов могут иметь одинаковое числовое значение.

0 голосов
/ 01 февраля 2019

Переменная-указатель ptr будет содержать адрес, с которого начинается var, т. Е. Адрес первого байта var.Если вы разыменуете ptr как *ptr, вы получите значение var.

Предполагая, что int равен 4 байта, использование *ptr "знает", чтобы прочитать следующие 4 байта из-за типа указателя.Поскольку ptr имеет тип int *, это означает, что *ptr имеет тип int, поэтому следующие 4 байта считываются как int.

Например:

int var = 4;
int *ptr = &var;
printf("ptr = %p\n", (void *)ptr);
printf("*ptr = %d\n", *ptr);
printf("&var = %p\n", (void *)&var);
printf("var = %d\n", var);

Вывод:

ptr = 0x7ffc330b4484
*ptr = 4
&var = 0x7ffc330b4484
var = 4
0 голосов
/ 01 февраля 2019

Как и любой другой тип, указатель также является типом.

Указатель, который может быть разыменован, должен указывать на полный тип.Каждый тип имеет определенный размер (до или пользовательский), поэтому разыменование учитывает размер типа объекта, на который указывает указатель.

Цитирование C11, глава §6.5.3.2

Унарный оператор * обозначает косвенность.Если операнд указывает на функцию, результатом является обозначение функции;если он указывает на объект, результатом является lvalue, обозначающее объект.Если операнд имеет тип «указатель на тип», результат имеет тип «тип».Если указателю было присвоено недопустимое значение, поведение унарного оператора * не определено.

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

Скажите,

int x = 10;
int * px = &a;

и на этой платформе sizeof(int) == 4.

Теперь в памяти это будет выглядеть как

          +-------------------------------------------+
          |                                           |
          |              int x  = 10;                 |
          |                                           |
          +-------------------------------------------+

      0x1000                                        0x1004


           +------------------------------------------+
           |                                          |
           |     pointer * px  = &x; (0x1000)         |
           |                                          |
           +------------------------------------------+
  • Теперь всеблок памяти, для этой переменной выделяется 0x1000 до 0x1003 (до начала адреса 0x1004) x типа int.
  • px - указатель, указывающий наначало этого блока памяти.Кроме того, при определении px мы сказали компилятору, что он будет указывать на int, так что компилятор знает, что блок памяти, который хранится в px, имеет хранилище на 4 байта, и для получения данных во время косвенного обращения(используя оператор *), нам нужно прочитать все 4 байта и затем вернуть результат.

Таким образом, при записи *px компилятор считывает все 4 байта и возвращает значение.

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