Арифметика с указателем C для необычных архитектур - PullRequest
0 голосов
/ 04 июня 2018

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

Предположим, у меня есть процессор с 64-разрядными регистрами, который подключен к ОЗУ, где каждый адрес соответствует ячейке 4 бита.широкий.Реализация для C для этой машины определяет CHAR_BIT равным 8. Предположим, я скомпилировал и выполнил следующие строки кода:

char *pointer = 0;
pointer = pointer + 1;

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

Теперь предположим, что у меня есть процессор с 12-разрядными регистрами, который подключен к ОЗУ, где каждый адрес соответствует ячейке шириной 4 бита.,Реализация C для этой машины определяет CHAR_BIT равным 12. Предположим, что те же строки кода скомпилированы и выполнены для этой машины.Будет ли указатель равен 3?

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

Ответы [ 6 ]

0 голосов
/ 05 июня 2018

Следующий фрагмент кода демонстрирует инвариант арифметики указателя C - независимо от того, что такое CHAR_BIT, независимо от того, какая аппаратная наименее адресуемая единица измерения, и независимо от того, каково действительное битовое представление указателейесть,

#include <assert.h>
int main(void)
{
    T x[2]; // for any object type T whatsoever
    assert(&x[1] - &x[0] == 1); // must be true
}

А так как sizeof(char) == 1 по определению , это также означает, что

#include <assert.h>
int main(void)
{
    T x[2]; // again for any object type T whatsoever
    char *p = (char *)&x[0];
    char *q = (char *)&x[1];
    assert(q - p == sizeof(T)); // must be true
}

Однако, если вы преобразуете в целые числа перед выполнением вычитания,инвариант испаряется:

#include <assert.h>
#include <inttypes.h>
int main(void);
{
    T x[2];
    uintptr_t p = (uintptr_t)&x[0];
    uintptr_t q = (uintptr_t)&x[1];
    assert(q - p == sizeof(T)); // implementation-defined whether true
}

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

0 голосов
/ 05 июня 2018

Когда вы увеличиваете указатель на символ, адрес равен CHAR_BIT, деленному на ширину ячейки памяти на машине?

На "обычной" машине -на самом деле, на подавляющем большинстве машин, где работает C - CHAR_BIT просто - это ширина ячейки памяти на машине, поэтому ответом на вопрос является "да" (поскольку CHAR_BIT / CHAR_BIT1.).

Машина с ячейками памяти, меньшими CHAR_BIT, была бы очень, очень странной - возможно, несовместимой с определением C.

Определение C говорит, что:

  • sizeof(char) точно равно 1.

  • CHAR_BIT, число битов в char не менее 8. То есть, насколькочто касается C, то байт не может быть меньше 8 бит.(Он может быть больше, и это удивляет многих, но это нас здесь не касается.)

  • Существует сильное предположение (если не явное требование), что char (или «байт») - это «минимально адресуемая единица» аппарата или что-то подобное.

Таким образом, для машины, которая может адресовать 4 бита за раз, нам нужно будет выбратьнеестественные значения для sizeof(char) и CHAR_BIT (которые в противном случае, вероятно, хотели бы быть 2 и 4, соответственно), и мы должны были бы игнорировать предположение, что тип char является минимальной адресуемой единицей машины.

C не требует внутреннего представления (битовой комбинации) указателя.Самая близкая переносимая C-программа может сделать что-нибудь с внутренним представлением значения указателя, это распечатать его, используя %p - и это явно определено как определяемое реализацией.

Так что я думаю, чтоЕдинственный способ реализовать C на «4-битной» машине - использовать код

char a[10];
char *p = a;
p++;

, генерирующий инструкции, которые фактически увеличивают адрес, стоящий за p, на 2.

.интересный вопрос, должен ли %p печатать фактическое, необработанное значение указателя или значение, разделенное на 2.

Было бы также очень интересно наблюдать за последующим фейерверком как за слишком умными программистами на такой машинеиспользовала методы упорядочения типов, чтобы получить в свои руки внутреннее значение указателей, чтобы они могли увеличивать их на на самом деле 1 - не на 2, которые всегда генерировали бы "правильные" добавления 1 - так, чтобы они моглиУдивите своих друзей, открыв странный кусок байта, или запутайте завсегдатаи на SO, задав вопроснс об этом.«Я только что увеличил указатель на 1. Почему %p показывает значение, которое на 2 больше?»

0 голосов
/ 04 июня 2018

Кажется, что путаница в этом вопросе проистекает из того факта, что слово "байт" в стандарте C не имеет типичного определения (которое составляет 8 бит).В частности, слово «байт» в стандарте C означает набор битов, где число битов определяется константой, определенной реализацией CHAR_BITS.Кроме того, «байт», как определено стандартом C, представляет собой наименьший адресуемый объект , к которому может обращаться программа C.

Это оставляет открытым вопрос о том, существует ли взаимно-однозначное соответствие между определением C «адресуемого» и аппаратным определением «адресуемого».Другими словами, возможно ли, что аппаратное обеспечение может обращаться к объектам, которые меньше, чем «байт»?Если (как в OP) «байт» занимает 3 адреса, то это означает, что «байтовые» обращения имеют ограничение на выравнивание.То есть 3 и 6 являются действительными "байтовыми" адресами, а 4 и 5 - нет.Это запрещено разделом 6.2.8, в котором обсуждается выравнивание объектов.

Это означает, что архитектура, предложенная OP, не поддерживается спецификацией C.В частности, реализация может не иметь указателей, которые указывают на 4-битные объекты, когда CHAR_BIT равен 12.


Вот соответствующие разделы из стандарта C:

§3.6 Определение «байт», используемое в стандарте

[Байт - это] адресуемая единица хранения данных, достаточно большая, чтобы вместить любой элемент базового набора символов среды выполнения.

ПРИМЕЧАНИЕ 1. Можно выразить адрес каждого отдельного байта объекта однозначно.

ПРИМЕЧАНИЕ 2. Байт состоит из непрерывной последовательности битов, число которых определяется реализацией.,Младший значащий бит называется младшим битом;самый старший бит называется старшим битом.

§5.2.4.2.1 описывает CHAR_BIT как

количество бит для наименьшего объекта, который не являетсябитовое поле (байт)

§6.2.6.1 ограничивает все объекты, которые больше чем символ, кратным битам CHAR_BIT:

[...]За исключением битовых полей, объекты состоят из смежных последовательностей из одного или нескольких байтов, число, порядок и кодирование которых либо явно указаны, либо определены реализацией.

[...] Значения, хранящиеся вобъекты не битового поля любого другого типа объекта состоят из n × CHAR_BIT битов, где n - размер объекта этого типа в байтах.

§6.2.8 ограничивает выравниваниеобъекты

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

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

§6.5.3.2 определяет sizeof a char и, следовательно, "байт"

Когда sizeof применяется к операнду с типом char, без знака или со знаком, или его квалифицированной версией, результат равен 1.

0 голосов
/ 04 июня 2018

char *pointer = 0;
После выполнения указатель равен 1

Не обязательно.Этот особый случай дает нулевой указатель, поскольку 0 является константой нулевого указателя.Строго говоря, такой указатель не должен указывать на действительный объект.Если вы посмотрите на фактический адрес, сохраненный в указателе, это может быть что угодно.

Нулевые указатели в стороне, язык C ожидает, что вы сделаете арифметику указателей, сначала указав на массив.Или, в случае char, вы также можете указать на фрагмент общих данных, таких как структура.Все остальное, как и ваш пример, является неопределенным поведением.

Реализация C для этой машины определяет CHAR_BIT равным 12

Стандарт C определяет charбыть равным байту, поэтому ваш пример немного странный и противоречивый.Арифметика указателя всегда будет увеличивать указатель, чтобы указывать на следующий объект в массиве.Стандарт на самом деле не говорит о представлении адресов вообще, но ваш вымышленный пример, который разумно увеличит адрес на 12 бит, потому что это размер char.

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

0 голосов
/ 04 июня 2018

Будет ли указатель равен 3?

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

Все, что мы знаем, это то, что добавление 1 к указателю на символ сделает точку указателяна следующем объекте char - где бы то ни было.Но ничего о значении указателей.

Поэтому, когда вы говорите, что

pointer = pointer + 1;

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

В большинстве систем char является 8-битным, а указатели являются (виртуальными) адресами памяти, ссылающимися на 8-битное адресное расположение памяти.В таких системах приращение указателя на символ увеличивает значение указателя (он же адрес памяти) на 1. Однако, на - необычных архитектурах - невозможно сказать.

Но если у вас есть система, в которой каждый адрес памятиссылается на 4 бита, а символ на 12 бит, кажется хорошим предположением, что ++pointer увеличит указатель на три.

0 голосов
/ 04 июня 2018

Указатели увеличиваются на минимум их ширины типа данных, на который они «указывают», но не гарантируется, что они точно увеличатся до этого размера.

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

Таким образом, в общем случае вы не можете считать этот указатель равным 3. Очень хорошо, что это может быть 3, 4 или какое-то большее число.

Вот пример.

struct char_three {
   char a;
   char b;
   char c;
};

struct char_three* my_pointer = 0;
my_pointer++;

/* I'd be shocked if my_pointer was now 3 */

Выравнивание памяти зависит от машины.Об этом нельзя обобщать, за исключением того, что большинство машин определяют WORD как первый адрес, который может быть выровнен по выборке памяти на шине.Некоторые машины могут указывать адреса, которые не совпадают с выборками шины.В таком случае выбор двух байтов, которые охватывают выравнивание, может привести к загрузке двух СЛОВ.

Большинство систем не принимают нагрузки WORD на невыровненных границах без жалоб.Это означает, что немного сборочной плиты котла применяется для перевода выборки к исходной границе СЛОВА, если требуется максимальная плотность.

Большинство компиляторов предпочитают скорость максимальной плотности данных, поэтому они выравнивают свои структурированные данные, чтобы использовать преимущества границ WORD, избегая дополнительных вычислений.Это означает, что во многих случаях данные, которые не выровнены тщательно, могут содержать «дыры» в байтах, которые не используются.

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

...