Подтвердить что ты не робот

Адресация и указатели памяти в C

Это взято из C и основывается на этом. Предположим, что у нас есть 32-битный указатель

char* charPointer;

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

int* intPointer;

также указывает на какое-то место в памяти, и если мы его увеличиваем, он знает, что он должен увеличиваться на 4 байта, если добавить к нему 1.

Вопрос в том, как мы можем адресовать полные 32 бита адресного пространства (2 ^ 32) - 4 гигабайта с этими указателями, если, очевидно, они содержат некоторую информацию в них, что позволяет их разделять друг от друга, например char* или int*, поэтому это оставляет нас не с 32 байтами, а с меньшим.

При вводе этого вопроса я пришел к мысли, может быть, это все синтаксический сахар и действительно для компилятора? Может быть, необработанный указатель всего 32 бит, и он не заботится о типе? Это так?

4b9b3361

Ответ 1

Вы можете быть смущены временем компиляции и временем выполнения.

Во время компиляции gcc (или любой компилятор C) знает тип указателя, в частности знает тип данных, указанных этой переменной указателя. Таким образом, gcc может испускать правильный машинный код. Таким образом, приращение переменной int * (на 32-битной машине, имеющей 32 бита int), преобразуется в инкремент 4 (байты), а приращение переменной char* преобразуется в приращение 1.

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

Типы (в программах на C) не известны во время выполнения.

Некоторые другие языки (Lisp, Python, Javascript,....) требуют, чтобы типы были известны во время выполнения. В недавнем С++ (но не C) некоторые объекты (имеющие виртуальные функции) могут иметь RTTI.

Ответ 2

Это действительно синтаксический сахар. Рассмотрим следующий фрагмент кода:

int t[2];
int a = t[1];

Вторая строка эквивалентна:

int a = *(t + 1); // pointer addition

что само по себе эквивалентно:

int a = *(int*)((char*)t + 1 * sizeof(int)); // integer addition

После того, как компилятор проверил типы, он отбрасывает приведение и работает только с адресами, длинами и целочисленным сложением.

Ответ 3

Да. Исходный указатель - это 32 бита данных (или 16 или 64 бита, в зависимости от архитектуры) и не содержит ничего. int *, char *, struct sockaddr_in * - это просто информация для компилятора, чтобы узнать, какой номер для фактического добавления при инкрементации, а также тип, который он будет иметь при разыменовании.

Ответ 4

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

int main()
{
    char * pc = 0;
    int * pi = 0;

    printf("%p\n", pc + 1);
    printf("%p\n", pi + 1);

    return 0;
}

Вы заметите, что добавление одного к char * увеличило его числовое значение на 1, делая то же самое с int *, увеличенным на 4 (что является размером int на моей машине).

Ответ 5

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

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

Ответ 6

Приращение указателя int * отличается от приращения char * только потому, что указательная переменная объявлена ​​как int *. Вы можете использовать int * для char *, а затем он будет увеличиваться с 1 байт.

Итак, да, это всего лишь синтаксический сахар. Он упрощает некоторые виды обработки массивов и смущает пользователей void *.