Как именно тип массива хранится в C? - PullRequest
0 голосов
/ 02 мая 2020

Итак, я читал «The C Programming Language» Брайана У. Кернигана и Денниса М. Рича ie, и все было ясно, пока я не попал в раздел «массив-указатель». Первое, что мы можем прочитать, это то, что по определению a[i] преобразуется в C в *(a+i). Хорошо, это понятно и логично. Следующее, что мы передаем массив как параметр функции, вы фактически передаете указатель на первый элемент этого массива. Затем мы обнаруживаем, что мы можем добавить целые числа к такому указателю, и даже допустимо иметь указатель на первый элемент после массива. Но потом написано, что мы можем вычитать указатели только в одном массиве.
Так как же C 'узнать', если эти два указателя указывают на один и тот же массив? Есть ли какая-то метаинформация, связанная с массивом? Или это просто означает, что это неопределенное поведение и компилятор даже не выдаст предупреждение? Хранится ли массив в памяти как обычные значения размера типа массива, одно за другим, или есть что-то еще?

Ответы [ 2 ]

1 голос
/ 02 мая 2020

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

На некоторых машинах полный адрес в памяти может иметь базу, которая представляет собой количество сегментов или другое блоки некоторого вида и смещение, которое является числом байтов на странице. Это было сделано потому, что, например, некоторые ранние аппаратные средства работали с данными в 16-битных фрагментах и ​​были предназначены для работы с 16-битными адресами, но более поздние версии аппаратного обеспечения, расширяющие ту же архитектуру, будут иметь более крупные адреса, но все равно будут использовать 16-битные адреса. кусочки данных, чтобы сохранить некоторую совместимость с предыдущим программным обеспечением. Таким образом, новое оборудование может иметь 22-разрядное адресное пространство. Старое программное обеспечение, использующее только 16-разрядные адреса, все равно будет вести себя одинаково, но более новое программное обеспечение может использовать дополнительный фрагмент данных для указания различных базовых адресов и, таким образом, получать доступ ко всей памяти в 22-разрядном адресном пространстве.

В таких случаях система, комбинация базы b и смещения o может относиться к адресу памяти 64 • b + o . Это дает доступ к полному 22-битному адресному пространству - с b = 65535 и o = 63 мы имеем 64 • b + o = 64 • 65535 + 63 = 4,194,303 = 2 22 * ​​1026 * -1.

Обратите внимание, что многие местоположения в форме могут быть доступны по нескольким адресам. Например, b = 17, o = 40 относится к тому же местоположению, что и b = 16, o = 104 и как b = 15, o = 168. Хотя формула для создания 22-битного адреса могла бы быть рассчитана на 65536 • b + o , и это дало бы каждой ячейке памяти уникальный адрес, перекрывающаяся формула используется, потому что это дает программисту гибкость в выборе их базы. Напомним, что эти машины были разработаны с использованием 16-битных фрагментов данных. При использовании схемы с неперекрывающимися адресами вам придется вычислять как основание, так и смещение при выполнении арифметики адреса c. С помощью схемы с перекрывающимися адресами вы можете выбрать базу для массива, с которым вы работаете, и затем выполнение любого арифметического адреса c требует вычисления только с смещенной частью.

Реализация C для этой архитектуры может легко поддерживать массивы до 65536 массивов, устанавливая один базовый адрес для массива, а затем выполняя арифметику c только со смещенной частью. Например, если у нас есть массив A 1000 int, и он размещается, начиная с ячейки памяти 78 976 (что равно 1234 • 64), мы можем установить b в 1234 и индексировать массив со смещением от 0 до 1998 (999 • 2, поскольку каждый int составляет два байта в этой реализации C).

Затем, если у нас есть указатель p, указывающий на A[125], он обозначается с помощью (1234, 250), чтобы указать на смещение 250 с основанием 1234. А если q указывает на A[55], то оно представляется с помощью (1234, 110). Чтобы вычесть эти указатели, мы игнорируем основание, вычитаем смещения и делим на размер одного элемента, поэтому получается (250-110) / 2 = 70.

Теперь, если у вас есть указатель r, указывающий на элемент 13 в некотором другом массиве B, он будет иметь другую базу, скажем, 2345. Таким образом, r будет представлен с помощью (2345, 26). Затем, чтобы вычесть r из p, нам нужно вычесть (2345, 26) из (1234, 250). В этом случае вы не можете игнорировать базы; простая работа со смещениями даст (250-26) / 2 = 112, но эти элементы не находятся на расстоянии 112 элементов (или 224 байта).

Компилятор может быть изменен для выполнения математических операций путем вычитания оснований , умножив на 64, и прибавьте это к разности смещений. Но затем он делает математику, чтобы вычесть указатели, которые совершенно не нужны в предполагаемом использовании арифметики указателей c. Таким образом, стандартный комитет C решил, что компилятор не должен поддерживать это, и способ указать, что поведение не определено, когда вы вычитаете указатели на элементы в разных массивах.

0 голосов
/ 02 мая 2020

... написано, что мы можем вычесть указатели только в одном массиве.
Так как же C 'узнать', если эти два указателя указывают на один и тот же массив?

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

int arr[100];
int *p1 = arr + 30;
int *p2 = arr + 50;
//both p1 and p2 point into arr
p2 - p1; //ok
p1 - p2; //ok
int *p3 = &((int)42); // ignore the C99 compound literal
//p3 does not point into arr
p3 - p1; //nope!
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...