Хранение неверного указателя автоматически неопределенное поведение?

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

Рассмотрим следующий код:

const char* str = "abcdef";
const char* begin = str;
if (begin - 1 < str) { /* ... do something ... */ }

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

Итак, это неопределенное поведение? Я никогда не думал, что это так, поскольку большая часть арифметики с указателями, похоже, полагается на подобные вещи, а указатель на самом деле является целым числом. Но недавно я услышал, что даже сам процесс загрузки недопустимого указателя в регистр является неопределенным поведением, поскольку некоторые архитектуры автоматически выдают ошибку шины или что-то подобное, если вы это сделаете. Может кто-нибудь указать мне на соответствующую часть стандарта C или C ++, которая решает эту проблему в любом случае?

 Nick21 сент. 2016 г., 06:37
Можете ли вы расширить вопрос - что если у вас есть для цикла, где вы пересекаете массив в обратном направлении? В этом обходе вам обязательно нужно проверить элемент перед первым, не разыменовывая его. У меня был похожий вопрос, но это был элемент после последнего.
 valdo21 сент. 2016 г., 06:16
Согласно стандарту C / C ++, это действительно неопределенное поведение. Но, честно говоря, я никогда не видел реальных ЦП / архитектуры, в которых вышеупомянутое является неопределенным поведением, то есть машин, которые не допускают произвольную арифметику указателей. И я видел довольно много архитектур, включая встроенные микроконтроллеры. Так что, по моему (скромному) мнению, код в порядке, если вы ограничиваетесь современными неэзотерическими архитектурами.

Ответы на вопрос(7)

Обоснование C99 [Сек. 6.5.6, последние 3 абзаца] объясняет, почему стандарт одобряет добавление1 на указатель, который указывает на последний элемент массива (p+1):

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

и почемуp-1 не подтверждено:

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

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

Обратите внимание, что целочисленное переполнение является примером стандарта для неопределенного поведения [сек. 3.4.3], так как это зависит от среды перевода и операционной среды. Я полагаю, что легко увидеть, что эта зависимость от среды распространяется и на недостаточное указание.

Вот почему стандарт явно делает его неопределенным поведением [в 6.5.6 / 8], как отмечено другими ответами здесь. Чтобы процитировать это предложение:

Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один после последнего элемента объекта массива, оценка не должна вызывать переполнение; в противном случае поведение не определено.

Смотрите также [сек. 6.3.2.3, последние 4 абзаца] обоснования C99, в котором дается более подробное описание того, как недействительные указатели могут быть сгенерированы, и какие эффекты это может иметь.

выражениеbegin - 1 делаетне вывести неверный указатель Это неопределенное поведение. Вам не разрешено выполнять арифметику указателей за пределами массива, над которым вы работаете. Таким образом, недопустимым является вычитание, а не сохранение полученного указателя.

 jalf30 мая 2015 г., 12:34
@supercat, зачем ОС это нужно? ОС не проходит тестирование объектов, которые вы создаете в своей программе ...
 jalf30 мая 2015 г., 19:55
Что нужно «извлечь» из этих правил для указателей в C ++, так это универсальность и эффективность. Он допускает более простые и более эффективные сравнения указателей на некоторых архитектурах (скажем, с архитектурой с сегментированной памятью, где возможность сделать эти упрощающие предположения позволяет упростить реализацию арифметики указателей и сравнений указателей).
 James McNellis01 окт. 2010 г., 15:22
И (4) результат приведения приводит к значению, которое представимоptrdiff_t (результат приведения может превышать максимальное значение, представляемоеptrdiff_t) [Это для C, где есть неявное преобразование из указателя в целое число; по крайней мере, это мое понимание этого. Я думаю, что то же самое верно для C ++; проблема в том, что преобразование указателя в целое число имеет результаты, определенные реализацией.]
 supercat30 мая 2015 г., 18:06
...memmove реализация, которая начиналась с фронта и проверялась на наличие коллизий перед записью каждого байта, но гораздо проще и проще сказать, что еслиdest>souceскопируйте сверху вниз и в противном случае скопируйте снизу вверх. Если указатели не связаны, не будет иметь значения, какой метод копирования выбран, при условии, что компилятор не использует «неопределенное поведение» в качестве предлога для того, чтобы сделать что-то раздражающее, например, вообще исключить операцию копирования.
 fizzer01 окт. 2010 г., 14:24
Обоснование C99 (ссылка на которое содержится в моем ответе) специально упоминает арифметику указателей за пределами массива как вывод недопустимых указателей.
 supercat29 мая 2015 г., 19:41
... эти гарантии часто могут быть более эффективными, чем алгоритмы, которые должны обходить их отсутствие. Например, было бы очень сложно написать эффективную операционную систему без средств проверки, идентифицирует ли указатель часть объекта, идентифицируемого базовым указателем и размером, носамый аппаратные платформы не будут иметь проблем с созданиемptr1 >= base && ptr1 < base+size показывают, что.
 jalf30 мая 2015 г., 18:16
@supercat семантика, которую вы запрашиваете, фактически является то, что обеспечиваетсяstd::less, :) (и, кроме того, конечно, ОС, как правило, не связана правилами C ++. Она предоставляет дополнительные гарантии, зависящие от реализации)
 jalf30 мая 2015 г., 19:51
Вы перемещаете ворота. Во-первых, вы говорите "ОСтребует эта особенность. Затем вы даете пример того, какmalloc мог быть реализован с использованием такой функции. А теперь вы приводите пример ОС, которая, как оказалось, не использовала эту конкретную функцию C ++, потому что она не была написана на C ++. Становится немного трудно понять, куда ты идешь с этим. C ++ должен поддерживать функцию, которую он уже поддерживает, потому что некоторые ОС не были написаны на C ++ и могли зависеть от поведения, определяемого реализацией, что делало эту функцию ненужной на уровне стандарта языкатем не мение?
 supercat31 мая 2015 г., 01:14
@jalf: Архитектура, в которой реляционные операторы на несвязанных указателях были бы дороги, могла бы легко соответствовать предложенному мной стандарту, просто установив макрос __POINTER_EXTENSIONS, чтобы указать, что он не предлагает стандартизированных расширений для функций, предусмотренных стандартом. Существующий код, который был написан для компилятора, где реляционные операторы просто «работают» и используют этот факт, и которыйнаверное не нужно ориентироваться на архитектуру, где они просто не будут работать, можно сделать «безопасным», добавив проверку необходимых расширений.
 supercat30 мая 2015 г., 18:38
@jalf: Я не думаю, что авторы Unix имели доступ к std :: less. Кроме того, наличие аппаратной платформы, которая может обеспечить функциональность, не принесет пользы, если компилятор решит ее исключить. Кроме того, хотя цель C состояла в том, чтобы упростить Unix, существует много видов библиотек, которые могут извлечь выгоду из различных гарантий, связанных с указателями. Хотя во многих случаях можно использовать параметры командной строки, чтобы попросить компиляторы разрешить их использовать программистам, не существует стандартного способа, с помощью которого код может проверить, вызывается ли компилятор таким образом, чтобы обеспечить необходимые гарантии.
 DevSolar01 окт. 2010 г., 15:13
Значение ptrdiff_t может быть рассчитано только для двух указателей на один и тот же объект данных. Единственное исключение из «в пределах массива» - указательодин за пределамиконец массива.
 supercat30 мая 2015 г., 18:04
@jalf: рассмотрим дизайнmalloc а такжеfree самих себя. Можно спроектировать систему malloc / free, в которой в заголовке каждого блока хранится достаточно информации, чтобы ей не пришлось «искать» блок, но при наличии множества небольших объектов такие издержки могут быть серьезными. Во многих случаях более практично иметь заголовок, в котором хранится достаточно информации, чтобы можно было обнаружить взаимосвязь блоков с другими блоками без особой работы, но некоторые алгоритмы для этого требуют сравнения между не связанными указателями. В этом отношении рассмотримmemmove, Можно спроектировать ...
 jalf01 окт. 2010 г., 15:19
@fizzer: У меня здесь нет стандарта C ++ (отформатировал мой компьютер несколько дней назад, и мне все еще нужно извлечь его из резервных копий), но он утверждает, что он не определен. Я не знаю, если С делает это по-другому, но я думаю, что это только то, что обоснование имеет дело с тем, чтона самом деле случается (на самом деле вы просто получаете недопустимый указатель), но стандарт более строг и гласит: «это бессмысленная операция, она не определена».
 Channel7201 окт. 2010 г., 14:26
Если выражение было изменено на(ptrdiff_t)begin - 1это все еще приведет к неопределенному поведению? Поскольку ptrdiff_t должен быть целочисленным типом со знаком, я думаю, что это будет хорошо.
 supercat31 мая 2015 г., 01:24
Я на самом деле не занимался программированием на C ++ как на C ++; единственное программирование на C ++, которое я сделал, - это кодирование уровня эмуляции, чтобы я мог запускать один и тот же код на 8-битном встроенном микро и на ПК (поскольку средства отладки на ПК намного лучше, чем у микро). C ++ позволяет определить тип, который ведет себя как 16-битный встраиваемых системunsigned int где умножение 0xFFFD на 0xFFFD дает 0x0009 [не UB]; хотя я думаю, что C должен предоставлять такие типы (они значительно облегчили бы миграцию большого количества кода более старой платформы на современные платформы), в настоящее время это может сделать только C ++.
 James McNellis01 окт. 2010 г., 15:20
@ Channel72: Да, пока все верно: (1)sizeof(ptrdiff_t) >= sizeof(void*) (это не обязательно гарантировано), (2) результат приведенияbegin целому типу со знакомptrdiff_t не приводит к минимальному значению, представляемому этим типом (если это так, то вычитание приведет к неопределенному поведению), и что (3) реализация определяет преобразование указателя в целое число последовательно, чтобы вы могли сравнить результат сравнения результата этого выражения с результатом(ptrdiff_t)str и получить значимый результат (также не гарантируется).
 supercat31 мая 2015 г., 01:19
Если код когда-либо будет работать на архитектурах, где< не может сравнивать несвязанные указатели так же легко, как связанные, затем изменить его на использованиеstd::less вместо< было бы улучшением, но если - как это будет более вероятно - никогда не будет работать на таких архитектурах, такое изменение было бы пустой тратой времени. Кроме того, мне нравится пытаться писать код, который будет работать на C или C ++; Я считаю расхождение языков неудачным, поскольку даже код, который не использует полиморфные объекты, мог бы извлечь выгоду из ряда возможностей C ++.
 supercat29 мая 2015 г., 19:37
@fizzer: логическое обоснование дает вескую причину, по которой реализациям следует позволять отлавливать даже такие вещи, как сравнения ранее действительных указателей, но мне интересно, предлагал ли кто-нибудь использовать в качестве стандарта макрос, такой как __POINTER_EXTENSIONS, реализация которого должна в некоторой степени задавать концептуальное значение например, __FP_EVAL_MODE, который будет указывать, какие виды операций реализация может поддерживать помимо тех, которые требуются стандартом. Многие реализации могут предложить гарантии того, как ведут себя указатели, выходящие далеко за рамки требований стандарта, и алгоритмы, которые могут использовать ...
 supercat30 мая 2015 г., 18:57
@jalf: Если компилятор поддерживает параметры командной строки для обеспечения согласованного поведения в ситуациях, не определенных в Стандарте, наличие в компиляторе также определения макросов для обозначения эффектов этих параметров должно быть тривиальным по сравнению. Кроме того, многие более старые компиляторы без таких опций могут быть обжалованы, просто имея предопределенные макросы компилятора (или make-файла), определяющие поведение компилятора. Лично, учитываяuint32_t n; Я не вижу ничего, что можно было бы сказать, еслиn*=n; требуется вести себя одинаково на всех платформах, гдеint или жеlong это 32 бита, как это на ...

ей. Помещение значения несопоставленного адреса в такой регистр может привести к сбою. Целочисленное переполнение / переполнение может привести к сбою. Поскольку C стремится работать на широком спектре платформ, указатели предоставляют механизм для безопасного программирования небезопасных схем.

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

Конечно, пример - плохой стиль, и нет веских причин для этого.

 supercat25 июл. 2017 г., 22:31
Еще одна проблема с правилами псевдонимов C состоит в том, что они основаны на динамическом содержании памяти, а не на статических аспектах структуры программы. Если в правилах указано, что при приведении указателя изT* вU*такое приведение создает «окно», во время которого указатель может использоваться для доступа к объектам типаT* или жеU*такие правила могут позволить больше оптимизаций, чем допустимо в соответствии с текущими правилами, а также разрешить использование большого количества кода, который в противном случае потребовал бы-fno-strict-aliasing.
 Potatoswatter25 июл. 2017 г., 21:44
@supercat Это хороший момент, и вы технически правы. Однако на практике, когда компилятор становится настолько агрессивным, чтоptr = arr - 1; становится неработоспособным (или сбой, или ...), его пользователи могут просто расстроиться, что они найдут другие компиляторы. Хотя стандарт допускает это, такое поведение настолько неуловимо патологично, и такие вычисления настолько распространены, что редко являются приемлемым решением.
 Potatoswatter25 июл. 2017 г., 22:00
@supercat Да, это еще один постоянный источник жалоб. Тем не менее, легче обнаружить такую ​​ошибку. За пределами вычислений иногда трудно избежать, и их трудно увидеть в коде. C ++ представляетstd::launder выборочно благословлять такие значения, но на самом деле указание этой функции было настолько странным, насколько вы могли ожидать.
 supercat21 июл. 2017 г., 17:26
Тот факт, что он четко определен на платформе, не делает его четко определенным во всех реализациях, нацеленных на эту платформу. На компиляторов, авторы которых больше заинтересованы в «оптимизации», чем в поддержке низкоуровневого программирования, нельзя полагаться на надежное поведение с таким кодом, даже если бы лежала базовая платформа.
 supercat25 июл. 2017 г., 21:54
Компиляторы, такие как gcc и clang, кажутся популярными, хотя их поведение в более разумные времена считалось бы возмутительным. Одна из причин, по которой авторы Стандарта сделали короткие неподписанные типы, рекламируемые как подписанные, заключается в том, что, согласно обоснованию, большинство текущих реализаций будет обрабатывать что-то вродеunsigned mul_mod_65535(unsigned short x, unsigned short y) { return (x*y) & 0xFFFF; } логичным образом, даже еслиx*y был больше чемINT_MAX, GCC, однако, иногда «оптимизирует» эту функцию таким образом, что это нарушается.
 supercat25 июл. 2017 г., 22:26
Основная проблема с такими вещами в C заключалась в том, что компиляторы, которые подходили для низкоуровневого программирования, обеспечивали бы необходимую семантику без директив, а программы, которые могли работать на сильно оптимизирующих компиляторах, не нуждались в такой семантике. Если бы стандарт определил такую ​​семантику, компиляторы, подходящие для низкоуровневого программирования, могли бы просто игнорировать их, когда они не требовались, но предусматривали режимы оптимизации для использования с программами, которые отмечали все места, где они делали что-то «хитрое».
Решение Вопроса

и он делает его неопределенным из-за упущения. Это определяет случайptr + I на 6,5,6 / 8 для

Если операнд-указатель указывает на элемент объекта массива, и массив достаточно велик, результат указывает на смещение элемента от исходного элемента, так что разность индексов результирующего и исходного элементов массива равна целочисленному выражению.Кроме того, если выражение P указывает на последний элемент объекта массива, выражение (P) +1 указывает один за последним элементом объекта массива, а если выражение Q указывает на один последний элемент последнего элемента массива, выражение (Q) -1 указывает на последний элемент объекта массива.

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

 Chubsdad01 окт. 2010 г., 15:11
@Martin York: стандарт C ++ определяет это как неопределенное поведение, даже если оно не разыменовано. Я надеюсь, что я подобрал соответствующую цитату в своем посте
 Davislor21 сент. 2016 г., 06:17
@supercat корректен: на некоторых процессорах загрузка неверного указателя в регистр само по себе приведет к сбою программы, поэтому гарантия того, что это сработает, отключит много оптимизаций.
 Martin York01 окт. 2010 г., 15:08
Это неопределенное или неопределенное поведение. Я ожидал бы, что код будет работать и работать и не будет иметь плохих последствий, хотя погода, в которую он вошел в ветку if, будет непостижимой (через стандарт).
 Davislor21 сент. 2016 г., 23:28
@supercat Я не уверен, с какой частью этого вы меня не согласны. Моя точка зрения заключалась в том, что даже архитектура, в которой ловушка указателей могла бы выполнить кучу работы, чтобы заставить код работать должным образом, даже если предполагается, что указатели вписываются в регистры общего назначения, и вы можете выполнять целочисленные вычисления с ними. В моем примереsize_t было бы 32-разрядным словом, ноptrdiff_t а такжеintptr_t будет хранить указатели в двух словах и делатьlong long int математика на них. Но это будет менее эффективно для этой архитектуры, чем проверка селекторов на равенство и выполнение операций ALU только на смещениях.
 Davislor21 сент. 2016 г., 20:14
@supercat Позвольте мне привести (патологический) пример того, что я имею в виду. Архитектура с 32-разрядными регистрами и 48-разрядными указателями, которые состоят из 16-разрядных селекторов сегментов и 32-разрядных смещений; сегменты относятся к отдельным областям памяти с различными разрешениями, управляемыми ОС. (Это поддерживает тип i386.) Добавление и вычитание сегментов - бессмысленная операция, которая в лучшем случае приведет к недопустимому или недопустимому селектору. Кроме того, даже загрузка такого неверного селектора в регистр сегмента вызывает аппаратную ошибку. Преобразование указателя в произвольную пару целых чисел и обратно стоит дорого.
 supercat21 сент. 2016 г., 16:20
@Lorehead: Гарантия того, что определенные действия не будут иметь побочных эффектов на целевой платформе, позволит компилятору оптимизировать операции, которые в противном случае потребовались бы для их предотвращения; если компилятор передает любые такие гарантии, которые он получает, программисту, программист может оптимизировать дополнительные операции, которые компилятор не смог. Передача таких гарантий программисту потребовала бы, чтобы компилятор воздерживался от определенных оптимизаций, но в тех случаях, когда программист мог бы использовать гарантии, их действительность, вероятно, превысит ...
 supercat21 сент. 2016 г., 16:22
... ценность "оптимизаций", которые полагаются на их отсутствие. Например, если программист знает, что конкретное действие будет безвредным в 9 из 10 мест, в котором оно будет выполнено, и может пропустить 9 из 10 проверок, которые могут помешать его повышению эффективности без ущерба для правильности, но не в том случае, если это единственный способ обеспечить компилятор генерирует код для необходимого, включающего проверки в девяти ненужных случаях.
 supercat01 окт. 2010 г., 17:52
Это поведение, которое может вызвать аппаратную ошибку оборудования, которая проверяет содержимое регистров указателей. Таким образом, это неопределенное поведение. Для конкретной реализации возможно и допустимо указать, что произойдет, если программы будут выполнять различные действия, которые в соответствии со стандартом вызывают неопределенное поведение. Если реализация соответствует своей спецификации, поведение будет четко определено. Если код выполняется в другой реализации, которая соответствует стандарту C, но не соответствует спецификациям этой конкретной реализации, программа может произойти сбой произвольным образом.
 supercat21 сент. 2016 г., 21:29
@Lorehead: гарантия того, что вычисления с использованием неверных указателей не будут перехватывать, будет дорогойна платформах, где они будут ловить, независимо от «оптимизации». Однако такие платформы встречаются редко. На оборудовании, которое никогда не будет перехватывать такие операции, компилятор, который гарантировал, что такие вычисления не будут иметь побочных эффектов, должен был бы лишиться некоторых «оптимизаций», но ценность таких гарантий для кода, который может их использовать, часто намного больше, чем значение утраченных "оптимизаций".
 supercat22 сент. 2016 г., 02:16
@Lorehead: Современное использование термина «оптимизация» относится к понятию, что компилятор должен агрессивно идентифицировать ситуации, которые будут вызывать UB, и заключить, что переменные не могут содержать значения, которые могут вызвать такие ситуации. Например, учитывая кодif (p != 0) doSomething(p); debug_log(*p); «современный» оптимизирующий компилятор может заключить, что было безопасно сделать вызовdoSomething безусловный, так как код будет вызывать UB, если «p» равно нулю, даже если на целевой платформе чтение нулевого указателя просто приведет к бессмысленному значению.

принятый ответ вэто тесно связанный вопрос, Присвоение недопустимого указателя переменной, сравнение недопустимого указателя, приведение недопустимого указателя вызывает неопределенное поведение.

$ 5.объекта массива или один после последнего элемента объекта массива, поведение не определено.75)»

Резюме, оно не определенодаже если вы не разыменовываете указатель.

 James McNellis01 окт. 2010 г., 15:13
Этот текст касается вычитания указателя из указателя; OP вычитает целое число из указателя.
 Matthieu M.01 окт. 2010 г., 16:33
Я не уверен в ваших рассуждениях, вычитая два указателя из разных массивов, у которых у вас могут возникнуть проблемы, потому что указатели указывают на разные зоны памяти (представьте, что память далеко / близко в 16-битной архитектуре). Здесь нет ничего о вмешательстве в сами указатели, на самом деле, довольно часто используются верхние биты 64-битных указателей для хранения дополнительных флагов.
 Chubsdad01 окт. 2010 г., 15:15
@ Джеймс МакНеллис: Я думаю, это арифметика с указателями. В конечном итоге речь идет о результирующем значении указателя

поведению. У меня нет здесь стандарта C, но я вижу «недопустимые указатели» в Обосновании:http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf

 fizzer01 окт. 2010 г., 14:36
Обратите внимание, что ptrdiff_t будет содержатьразница между указатели, а не сами указатели. Это не одно и то же.
 fizzer01 окт. 2010 г., 14:30
Не неопределенное поведение, но результат определяется реализацией. То есть ваша реализация документирует некоторое разумное поведение, но оно не будет переносимым и может быть бесполезным.
 fizzer01 окт. 2010 г., 14:32
FAQ по comp.lang.c решает эту проблему:c-faq.com/ptrs/int2ptr.html, Как я уже сказал, у меня нет Стандарта.
 Channel7201 окт. 2010 г., 14:09
Если это так, не могли бы вы просто привести все свои указатели кptrdiff_t при выполнении арифметики с указателями? Другими словами, если я изменил приведенный выше пример кода, чтобы прочитатьif ((ptrdiff_t)begin - 1) это больше не будет неопределенным поведением?

Ваш ответ на вопрос