Безопасно ли проверять отношения между указателями для итерации? - PullRequest
4 голосов
/ 14 июня 2019

Пожалуйста, рассмотрите массив Thing. Это своего рода стек.

На него указывает thingsBottom, а на его пустой конец указывает thingsTop.

РЕДАКТИРОВАТЬ: Каждый раз, когда я хочу что-то добавить в список, я делаю *thingsTop = newThing; thingsTop++;.

Я бы хотел перебрать его с конца до начала, используя указатели, например:

for (Thing* thing = thingsTop - 1; thing >= thingsBottom; thing--) {
    doSomething(*thing);
}

Гарантируется ли это всегда, независимо от используемой конкретной реализации C?

Можно ли сказать thing >= thingsBottom?

Ответы [ 2 ]

5 голосов
/ 14 июня 2019

Это гарантированно всегда работает, независимо от конкретного C реализация используется?

Можно ли сказать thing >= thingsBottom?

Нет, и не безоговорочно.

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

Вы можете написать такой цикл; вам просто нужно проверить, прежде чем уменьшать:

for (Thing* thing = thingsTop; thing > thingsBottom; ) {
    thing--;
    doSomething(*thing);
}
3 голосов
/ 14 июня 2019

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

Чтобы разрешить подобный код, C имеет специальное правило, которое позволяет вам иметь указатель 1 элемент за пределами массива, и это безопасно, если вы не отмените ссылку на этот элемент. указатель, когда он указывает на 1 элемент.

Это означает, что вы должны написать цикл с повышением, а не с понижением. Довольно каноническим примером этого будет:

thing* begin = thing_array;
thing* end   = thing_array + size; // point 1 past the last valid item

for(thing* i = begin; i != end; i++)
{
   do_stuff(i);
}

Например, именно так работает класс контейнера C ++ ::iterator.

Если вам действительно нужно выполнить итерацию с конца до начала, я бы порекомендовал целочисленный итератор:

for(size_t i=0; i<size; i++)
{
  do_stuff(thing_array[size-i-1]);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...