В главе 5 K & R (язык программирования C, 2-е издание) я прочитал следующее:
Во-первых, указатели могут сравниваться при определенных обстоятельствах. Если
p
иq
указывают на элементы одного и того же массива, то соотношения нравится==
,!=
,<
,>=
и т.д. работать должным образом.
Кажется, это означает, что сравнивать можно только указатели, указывающие на один и тот же массив.
Однако, когда я попробовал этот код
char t = 't';
char *pt = &t;
char x = 'x';
char *px = &x;
printf("%d\n", pt > px);
1
выводится на экран.
Прежде всего, я думал, что получу неопределенный или какой-то тип или ошибку, потому что pt
и px
не указываю на один и тот же массив (по крайней мере, в моем понимании).
Кроме того, pt > px
потому что оба указателя указывают на переменные, хранящиеся в стеке, и стек растет, поэтому адрес памяти t
больше, чем адрес x
? Вот почему pt > px
это правда?
Я запутываюсь, когда вводится malloc. Также в K & R в главе 8.7 написано следующее:
Тем не менее, есть еще одно предположение, что указатели на различные возвращаемые блоки
sbrk
могут быть значительно сопоставлены. Это не гарантируется стандартом, который разрешает сравнение указателей только внутри массива. Таким образом, эта версияmalloc
является переносимой только среди машин, для которых общее сравнение указателей имеет смысл.
У меня не было проблем со сравнением указателей, указывающих на пространство, расположенное в куче, с указателями, указывающими на переменные стека.
Например, следующий код работал нормально с 1
печатью:
char t = 't';
char *pt = &t;
char *px = malloc(10);
strcpy(px, pt);
printf("%d\n", pt > px);
Основываясь на моих экспериментах с моим компилятором, я склоняюсь к мысли, что любой указатель можно сравнить с любым другим указателем, независимо от того, куда он указывает отдельно. Более того, я думаю, что арифметика указателей между двумя указателями - это хорошо, независимо от того, куда они указывают отдельно, потому что арифметика просто использует адреса памяти, которые хранят указатели.
Тем не менее, меня смущает то, что я читаю в K & R.
Я спрашиваю, потому что мой проф. фактически сделал это экзаменационным вопросом. Он дал следующий код:
struct A { char *p0; char *p1; }; int main(int argc, char **argv) { char a = 0; char *b = "W"; char c[] = [ 'L', 'O', 'L', 0 ]; struct A p[3]; p[0].p0 = &a; p[1].p0 = b; p[2].p0 = c; for(int i = 0; i < 3; i++) { p[i].p1 = malloc(10); strcpy(p[i].p1, p[i].p0); } }
Что они оценивают:
p[0].p0 < p[0].p1
p[1].p0 < p[1].p1
p[2].p0 < p[2].p1
Ответ 0
, 1
и 0
.
(Мой профессор включает в себя заявление об отказе на экзамене, что вопросы касаются среды программирования Ubuntu Linux 16.04, 64-битная версия)
(примечание редактора: если SO допускает больше тегов, эта последняя часть будет требовать x86-64 , linux и, возможно, сборки . Если бы вопрос / класс был конкретно о деталях реализации ОС низкого уровня, а не о переносимых C.)
C
с тем, что является безопасным вC
. Однако всегда можно выполнить сравнение двух указателей с одним и тем же типом (например, с помощью проверки на равенство), используя арифметику указателей и сравнение,>
и<
это безопасно только при использовании в данном массиве (или блоке памяти).Ответы:
Согласно стандарту C11 , реляционные операторы
<
,<=
,>
, и>=
могут быть использованы только на указатели на элементы одного и того же массива или структуры объекта. Это прописано в разделе 6.5.8p5:Обратите внимание, что любые сравнения, которые не удовлетворяют этому требованию, вызывают неопределенное поведение , что означает (помимо прочего), что вы не можете зависеть от результатов, которые можно повторить.
В вашем конкретном случае, как для сравнения адресов двух локальных переменных, так и адреса локального и динамического адреса, операция «работала», однако результат мог измениться, внеся, казалось бы, несвязанные изменения в ваш код или даже компилирование одного и того же кода с различными настройками оптимизации. С неопределенным поведением то, что код может привести к сбою или появлению ошибки, не означает, что это произойдет .
Например, процессор x86, работающий в реальном режиме 8086, имеет сегментированную модель памяти, использующую 16-битный сегмент и 16-битное смещение для построения 20-битного адреса. Так что в этом случае адрес не преобразуется точно в целое число.
Операторы равенства
==
и тем не!=
менее не имеют этого ограничения. Их можно использовать между любыми двумя указателями на совместимые типы или указателями NULL. Таким образом, использование==
или!=
в обоих ваших примерах приведет к созданию корректного C-кода.Тем не менее, даже с
==
и!=
вы можете получить некоторые неожиданные, но все еще четко определенные результаты. См. Может ли сравнение равенства не связанных между собой указателей оценить как истинное? для более подробной информации об этом.Что касается вопроса об экзамене, заданного вашим профессором, он делает ряд ошибочных предположений:
Если бы вы запускали этот код на архитектуре и / или с компилятором, который не удовлетворяет этим предположениям, вы могли бы получить совсем другие результаты.
Кроме того, оба примера также показывают неопределенное поведение, когда они вызывают
strcpy
, поскольку правый операнд (в некоторых случаях) указывает на один символ, а не на строку с нулевым символом в конце, что приводит к чтению функции за пределами заданной переменной.источник
<
междуmalloc
результатом и локальной переменной (автоматическое хранение, то есть стек), он может предположить, что путь выполнения никогда не используется, и просто скомпилировать всю функцию вud2
инструкцию (вызывает недопустимый -инструкция исключения, которое ядро будет обрабатывать, передавая SIGILL процессу). GCC / clang делают это на практике для других видов UB, например, выпадая из-за отказаvoid
функции. Кажется, что godbolt.org сейчас недоступен, но попробуйте скопировать / вставитьint foo(){int x=2;}
и заметить отсутствиеret
malloc
используется для получения большего объема памяти от ОС, поэтому нет оснований предполагать, что ваши локальные переменные (стек потоков) превышаютmalloc
динамически выделяемые место хранения.int x,y;
реализацию ...Основная проблема со сравнением указателей на два разных массива одного и того же типа заключается в том, что сами массивы не нужно размещать в определенном относительном положении - один может оказаться до и после другого.
Нет, результат зависит от реализации и других непредсказуемых факторов.
Там не обязательно стек . Когда оно существует, оно не должно расти. Это может вырасти. Это может быть несмежно каким-то странным образом.
Давайте посмотрим на спецификацию C , §6.5.8 на странице 85, в которой обсуждаются реляционные операторы (то есть операторы сравнения, которые вы используете). Обратите внимание, что это не относится к прямой
!=
или==
сравнения.Последнее предложение важно. Хотя я сократил некоторые несвязанные случаи для экономии места, для нас важен один случай: два массива, а не часть одного и того же объекта struct / aggregate 1 , и мы сравниваем указатели на эти два массива. Это неопределенное поведение .
Хотя ваш компилятор только что вставил какую-то машинную инструкцию CMP (сравнить), которая численно сравнивает указатели, и вам повезло, UB - довольно опасный зверь. Буквально все может случиться - ваш компилятор может оптимизировать всю функцию, включая видимые побочные эффекты. Это может породить носовых демонов.
1 Можно сравнивать указатели на два разных массива, которые являются частью одной и той же структуры, поскольку это подпадает под предложение, в котором два массива являются частью одного и того же агрегатного объекта (структуры).
источник
t
иx
будучи определенными в одной и той же функции, нет никаких оснований предполагать, что компилятор, нацеленный на x86-64, будет размещать локальные объекты в кадре стека для этой функции. Стек, растущий вниз, не имеет ничего общего с порядком объявления переменных в одной функции. Даже в отдельных функциях, если один может быть встроен в другой, то локальные элементы «дочерней» функции все еще могут смешиваться с родителями.void
функции не работает) g ++ и clang ++ действительно делают это на практике: godbolt.org/z/g5vesB они предположим, что путь выполнения не выбран, потому что он ведет к UB, и скомпилируем любые такие базовые блоки в недопустимую инструкцию. Или вообще без инструкций, просто молча проваливаясь к следующему асму, если эта функция когда-либо вызывалась. (По какой-то причинеgcc
не делает этого, толькоg++
).Эти вопросы сводятся к:
И ответом на все три является «определение реализации». Вопросы вашего профессора являются поддельными; они основали его в традиционном формате Unix:
но несколько современных объединений (и альтернативных систем) не соответствуют этим традициям. Если только они не поставили вопрос перед «с 1992 года»; убедитесь, что дали -1 на eval.
источник
arr[]
является таким объектом, то стандарт будет предписыватьarr+32768
сравнение, превышающееarr
даже то, что при сравнении с указателем со знаком будет указано иное.Практически на любой удаленно-современной платформе указатели и целые числа имеют изоморфное отношение порядка, а указатели на непересекающиеся объекты не чередуются. Большинство компиляторов выставляют этот порядок программистам, когда оптимизации отключены, но Стандарт не делает различий между платформами, которые имеют такой порядок, и платформами, которые не имеют и не требуют, чтобы какие-либо реализации представляли такой порядок программисту даже на платформах, которые определить это. Следовательно, некоторые авторы компиляторов выполняют различные виды оптимизаций и «оптимизаций», основываясь на предположении, что код никогда не будет сравнивать использование реляционных операторов в указателях с различными объектами.
Согласно опубликованному обоснованию, авторы Стандарта предполагали, что реализации расширяют язык, определяя, как они будут вести себя в ситуациях, которые Стандарт характеризует как «неопределенное поведение» (т. Е. Когда Стандарт не предъявляет требований ), когда это будет полезно и практично. , но некоторые авторы компиляторов предпочли бы, чтобы программы никогда не пытались извлечь выгоду из чего-то помимо того, что предписывает Стандарт, чем позволять программам эффективно использовать поведение, которое платформы могут поддерживать, без каких-либо дополнительных затрат.
Мне неизвестны какие-либо коммерчески разработанные компиляторы, которые делают что-то странное с сравнением указателей, но по мере того, как компиляторы переходят на некоммерческую LLVM для своей серверной части, они все чаще обрабатывают бессмысленный код, поведение которого было задано ранее. компиляторы для своих платформ. Такое поведение не ограничивается реляционными операторами, но может даже влиять на равенство / неравенство. Например, несмотря на то, что в стандарте указано, что сравнение между указателем на один объект и указателем «только что прошедший» на непосредственно предшествующий объект будет сравниваться равным, компиляторы на основе gcc и LLVM склонны генерировать бессмысленный код, если программы выполняют такие сравнения.
В качестве примера ситуации, когда даже сравнение на равенство ведет себя бессмысленно в gcc и clang, рассмотрим:
И clang, и gcc сгенерируют код, который всегда будет возвращать 4, даже если
x
это десять элементов,y
сразу после него, иi
равен нулю, что приводит к тому, что сравнение истинно иp[0]
записывается со значением 1. Я думаю, что происходит то, что один проход оптимизации переписывает функции как будто*p = 1;
были заменены наx[10] = 1;
. Последний код был бы эквивалентен, если бы компилятор интерпретировался*(x+10)
как эквивалентный*(y+i)
, но, к сожалению, нижестоящий этап оптимизации признает, что доступ к немуx[10]
будет определен только в том случае, если будетx
иметь по крайней мере 11 элементов, что сделает невозможным влияние этого доступаy
.Если компиляторы могут получить такое «креативное» с помощью сценария равенства указателей, который описан в Стандарте, я бы не стал доверять им, чтобы они воздерживались от еще более креативного в тех случаях, когда Стандарт не предъявляет требований.
источник
Все просто: сравнивать указатели не имеет смысла, так как ячейки памяти для объектов никогда не будут в том порядке, в котором вы их объявили. Исключение составляют массивы. & массив [0] ниже, чем & массив [1]. Вот на что указывает K & R. На практике адреса членов структуры также расположены в том порядке, в котором вы их объявляете по моему опыту. Никаких гарантий на это .... Еще одно исключение, если вы сравниваете указатель на равный. Когда один указатель равен другому, вы знаете, что он указывает на тот же объект. Что бы это ни было. Плохой экзаменационный вопрос, если вы спросите меня. В зависимости от Ubuntu Linux 16.04, 64-битной версии среды программирования для экзаменационного вопроса? В самом деле ?
источник
arr[0]
,arr[1]
и т.д. отдельно. Вы объявляетеarr
как единое целое, поэтому упорядочение отдельных элементов массива отличается от описанного в этом вопросе.memcpy
для копирования смежной части структуры и влиять на все элементы в ней и не влиять на что-либо еще. Стандарт небрежно относится к терминологии относительно того, какие виды арифметики указателей могут быть сделаны со структурами илиmalloc()
выделенным хранилищем.offsetof
Макрос будет довольно бесполезным , если один не мог с таким же арифметиками указателей с байтами структуры , как сchar[]
, но стандарт не прямо сказать , что байты структуры являются (или могут быть использованы в качестве) объект массива.Какой провокационный вопрос!
Даже беглое сканирование ответов и комментариев в этой теме покажет, насколько эмоциональным окажется ваш, казалось бы, простой и понятный запрос.
Это не должно удивлять.
Бесспорно, недопонимание вокруг концепции и использования в указателях представляет собой доминирующую причину серьезных сбоев в программировании в целом.
Признание этой реальности становится очевидным в повсеместном распространении языков, разработанных специально для решения и, предпочтительно, чтобы избежать проблем, которые указатели вообще ставят. Думайте C ++ и другие производные от C, Java и его отношений, Python и другие скрипты - просто как более выдающиеся и распространенные, и более или менее упорядоченные в серьезности решения проблемы.
Поэтому более глубокое понимание основополагающих принципов должно быть уместным для каждого человека, стремящегося к совершенству в программировании, особенно на системном уровне .
Я полагаю, это именно то, что ваш учитель хочет продемонстрировать.
И природа C делает его удобным средством для этого исследования. Менее ясно, чем сборка - хотя, возможно, более легко понятное - и все же гораздо более явно, чем языки, основанные на более глубокой абстракции среды выполнения.
Разработанный для облегчения детерминированного перевода намерений программиста в инструкции, которые могут понять машины, C является языком системного уровня . Хотя классифицируется как высокий уровень, он действительно относится к категории «средний»; но поскольку такого не существует, обозначение «система» должно быть достаточным.
Эта характеристика в значительной степени ответственна за это языком выбора для драйверов устройств , операционной системы коды и встраиваемых реализаций. Кроме того, заслуженно предпочтительная альтернатива в приложениях, где оптимальная эффективность имеет первостепенное значение; где это означает разницу между выживанием и исчезновением, и, следовательно, является необходимостью, а не роскошью. В таких случаях привлекательное удобство переносимости теряет всю свою привлекательность, и выбор производительности с наименьшим общим значением для наименее распространенного знаменателя становится немыслимо вредным вариантом.
Что делает C - и некоторые его производные - совершенно особенным, так это то, что он позволяет пользователям полностью контролировать - когда это то, что они хотят - без наложения на них соответствующих обязанностей , когда они этого не делают. Тем не менее, он никогда не предлагает больше, чем самая тонкая изоляция от машины , поэтому правильное использование требует тщательного понимания концепции указателей .
По сути, ответ на ваш вопрос чрезвычайно прост и приятен - в подтверждение ваших подозрений. При условии , однако, что один придает необходимое значение для каждого понятия в этом заявлении:
Первым из них является как всегда безопасно и потенциально собственно , в то время как последние могут только когда - либо быть собственно , когда она была создана , как сейф . Удивительно - для некоторых - так что обоснованность последнего зависит от первогои требует его.
Конечно, часть путаницы возникает из-за эффекта рекурсии, присущей принципу указателя, - и проблем, возникающих при дифференциации контента от адреса.
У вас довольно правильно предположили,
И несколько авторов подтвердили: указатели - это просто числа. Иногда что-то ближе к комплексным числам, но все же не больше, чем числа.
Забавная резкость, в которой это утверждение было получено здесь, раскрывает больше о человеческой природе, чем программирование, но остается достойной внимания и разработки. Возможно, мы сделаем это позже ...
Как один комментарий начинает намекать; вся эта путаница и замешательство проистекают из необходимости отличать то, что действительно, от того, что безопасно , но это упрощение. Мы также должны различать, что является функциональным, а что надежным , что практично, а что может быть правильным , и, кроме того, то, что является правильным в определенных обстоятельствах, от того, что может быть правильным в более общем смысле . Не считая; разница между соответствием и уместностью .
С этой целью, в первую очередь необходимо оценить именно то , что указатель находится .
Как уже отмечали некоторые: термин указатель - это просто специальное имя для того, что является просто индексом , и, следовательно, не более чем любой другой число .
Это уже должно быть самоочевидным, принимая во внимание тот факт, что все современные обычные компьютеры являются двоичными машинами, которые обязательно работают исключительно с числами и на них . Квантовые вычисления могут изменить это, но это маловероятно, и оно не достигло совершеннолетия.
Технически, как вы заметили, указатели являются более точными адресами ; очевидное понимание, которое естественно вводит полезную аналогию соотнесения их с «адресами» домов или участков на улице.
В квартире модели памяти: вся системная память организована в одной линейной последовательности: все дома в городе лежат на одной дороге, и каждый дом уникально идентифицируется только по его номеру. Восхитительно просто.
В сегментированных схемах: иерархическая организация пронумерованных дорог вводится выше, чем нумерованных домов, поэтому требуются составные адреса.
Приводит нас к дальнейшему повороту, который превращает головоломку в такой захватывающе сложный клубок . Выше было целесообразно предположить, что указатели являются адресами, для простоты и ясности. Конечно, это не правильно. Указатель является не адрес; указатель является ссылкой на адрес , он содержит адрес . Как конверт спортивная ссылка на дом. Созерцание этого может привести к тому, что вы поймете, что подразумевалось под предложением рекурсии, содержащимся в концепции. По-прежнему; у нас есть только так много слов, и говорить о том адресах ссылок на адресаи так, скоро глохнет большинство мозгов внедопустимое исключение кода операции . И по большей части намерение легко получается из контекста, поэтому давайте вернемся на улицу.
Почтовые работники в этом нашем воображаемом городе очень похожи на тех, кого мы находим в «реальном» мире. Никто, скорее всего, не перенесет инсульт, когда вы говорите или спрашиваете о недействительном адресе, но каждый последний будет отказываться, когда вы просите его действовать в соответствии с этой информацией.
Предположим, что на нашей единственной улице всего 20 домов. Далее притворимся, что какая-то заблудшая или дислексичная душа направила письмо, очень важное, на номер 71. Теперь мы можем спросить нашего носителя Фрэнка, есть ли такой адрес, и он просто и спокойно скажет: нет . Мы даже можем ожидать , что он оценить , насколько далеко за пределами улицы это место будет лежать , если она действительно существует: примерно в 2,5 раза дальше , чем в конце. Ничто из этого не вызовет у него никакого раздражения. Однако, если мы попросим его доставить это письмо или забрать предмет из этого места, он, скорее всего, будет совершенно откровенен в отношении своего неудовольствия и отказа подчиниться.
Указатели - это просто адреса, а адреса - это просто числа.
Проверьте вывод следующего:
Называйте это на столько указателей, сколько хотите, действительных или нет. Пожалуйста , опубликуйте свои выводы, если они не удаются на вашей платформе, или ваш (современный) компилятор жалуется.
Теперь, потому что указатели являются просто числами, сравнивать их неизбежно. В каком-то смысле это именно то, что демонстрирует ваш учитель. Все следующие утверждения совершенно верны и правильны! - C, и при компиляции будет работать без проблем , даже если ни один указатель не будет инициализирован и поэтому содержащиеся в них значения могут быть неопределенными :
result
явно рассчитываем для ясности и печатаем его, чтобы заставить компилятор вычислять то, что в противном случае было бы избыточным, мертвым кодом.Конечно, программа плохо сформирована, когда либо a, либо b не определены (читай: неправильно инициализированы ) в момент тестирования, но это совершенно не имеет отношения к этой части нашего обсуждения. Эти фрагменты, а тоже из следующих утверждений, которые гарантированы - по «стандартной» - для компиляции и запуска безупречно, несмотря на IN -validity любого указателя вовлеченного.
Проблемы возникают только при разыменовании неверного указателя . Когда мы просим Фрэнка забрать или доставить по неверному, несуществующему адресу.
Дан любой произвольный указатель:
Пока это утверждение должно скомпилироваться и выполнить:
... как это должно быть:
... следующие два, по контрасту, по - прежнему легко собирать, но не в состоянии в исполнении , если указатель не является действительным - с помощью которого мы здесь всего лишь означает , что он ссылается на адрес , по которому данное приложение было предоставлен доступ :
Насколько тонкие изменения? Различие заключается в разнице между значением указателя, который является адресом, и значением содержимого: дома с этим номером. Никаких проблем не возникает, пока указатель не будет разыменован ; пока не будет предпринята попытка получить доступ к адресу, на который он ссылается. В попытке доставить или забрать посылку за пределы дороги ...
В более широком смысле , тот же принцип обязательно относится к более сложным примерам, включая вышеупомянутую необходимость в создании необходимой достоверности:
Реляционное сравнение и арифметика предлагают идентичную полезность для проверки эквивалентности и эквивалентно действительны - в принципе. Однако то , что означают результаты таких вычислений , - это совсем другое дело, и именно эта проблема решается в приведенных вами цитатах.
В C массив представляет собой непрерывный буфер, непрерывный линейный ряд областей памяти. Сравнение и арифметика, применяемые к указателям, которые ссылаются на местоположения в пределах такого единственного ряда, естественно, и, очевидно, имеют смысл как по отношению друг к другу, так и к этому «массиву» (который просто идентифицируется базой). Точно то же самое относится к каждому блоку, выделенному через
malloc
, илиsbrk
. Поскольку эти отношения неявны , компилятор может установить между ними действительные отношения и, следовательно, может быть уверен, что вычисления обеспечат ожидаемые ответы.Выполнение подобной гимнастики на указателях , которые ссылаются на отдельные блоки или массивы не предлагает такие присущее , и очевидно , полезности. Тем более что любое отношение, существующее в один момент, может быть признано недействительным из-за перераспределения, которое с большой вероятностью изменится, даже будет инвертировано. В таких случаях компилятор не может получить необходимую информацию для подтверждения уверенности в предыдущей ситуации.
Вы , однако, как программист, можете иметь такие знания! И в некоторых случаях обязаны использовать это.
Там ЯВЛЯЮТСЯ Таким образом, обстоятельства , при которых даже это полностью ДЕЙСТВИТЕЛЕН и совершенно PROPER.
Фактически, это именно то , что
malloc
нужно делать внутренне, когда приходит время объединять исправленные блоки - на подавляющем большинстве архитектур. То же самое верно для распределителя операционной системы, как это позадиsbrk
; если более очевидно , часто , на более разрозненных объектах, более критично - и актуально также на платформах, где этогоmalloc
не может быть. А сколько таких не написано на С?Обоснованность, безопасность и успех действия неизбежно являются следствием уровня понимания, на котором они основаны и применяются.
В предложенных вами цитатах Керниган и Ричи рассматривают тесно связанный, но, тем не менее, отдельный вопрос. Они определяющие те ограничения на язык , и объяснить , как вы можете использовать возможности компилятора , чтобы защитить вас , по крайней мере обнаружения потенциально ошибочные конструкции. Они описывают длины, на которые механизм способен - разработан - пойти, чтобы помочь вам в вашей задаче программирования. Компилятор ваш слуга, вы являетесь мастером. Мудрый господин, однако, хорошо знаком с возможностями своих различных слуг.
В этом контексте неопределенное поведение служит для указания на потенциальную опасность и возможность причинения вреда; не подразумевать неизбежной, необратимой гибели или конца света, каким мы его знаем. Это просто означает, что мы - «имея в виду компилятор» - не в состоянии сделать какие-либо предположения о том, что это может быть, или представить, и по этой причине мы решили помыть руки. Мы не будем нести ответственность за любые несчастные случаи, которые могут возникнуть в результате использования или неправильного использования этого средства .
По сути, он просто говорит: «За этим пунктом, ковбой : ты сам по себе ...»
Ваш профессор стремится продемонстрировать тончайшие нюансы .
Обратите внимание, какую большую осторожность они проявили при разработке своего примера; и как хрупко это все еще . Принимая адрес
a
, вкомпилятор принудительно выделяет фактическое хранилище для переменной, а не помещает его в регистр. Однако, поскольку это автоматическая переменная, программист не имеет никакого контроля над тем, где она назначена, и поэтому не может сделать какие-либо обоснованные предположения о том, что последует за ней. Вот почему
a
должен установить равным нулю, чтобы код работал как положено.Просто изменив эту строку:
к этому:
приводит к тому, что поведение программы становится неопределенным . Как минимум, первый ответ теперь будет 1; но проблема гораздо более зловещая.
Теперь код приглашает к катастрофе.
Несмотря на то, что он по-прежнему совершенно действителен и даже соответствует стандарту , он в настоящее время плохо сформирован и, хотя он обязательно компилируется, может не исполниться по разным причинам. На данный момент не существует множество проблем - ни один из которых компилятор находится в состоянии , чтобы распознать.
strcpy
будет начинаться с адресаa
, и продолжаться дальше, чтобы потреблять - и передавать - байт за байтом, пока не встретится ноль.p1
Указатель был инициализирован к блоку ровно 10 байт.Если
a
случится, что он будет помещен в конец блока, и у процесса нет доступа к тому, что следует, то самое следующее чтение - из p0 [1] - вызовет ошибку сегмента. Этот сценарий маловероятен для архитектуры x86, но возможен.Если область за пределами адреса
a
является доступной, не будут происходить никаких ошибок чтения, но программа все еще не спасена от несчастья.Если случится возникновение нулевого байта в пределах десяти, начиная с адреса
a
, он все еще может выжить, поскольку затемstrcpy
остановится и, по крайней мере, мы не будем страдать от нарушения записи.Если он не поврежден для чтения неправильно, но нулевой байт не встречается в этом диапазоне 10,
strcpy
он продолжит и попытается записать за пределы блока, выделенногоmalloc
.Если эта область не принадлежит процессу, segfault должен быть немедленно запущен.
Еще более катастрофическая - и тонкая --- ситуация возникает , когда следующий блок находится в собственности процесса, то ошибка не может быть обнаружена, сигнал не может быть повышена, и таким образом это может «появиться» еще «работать» , в то время как на самом деле он будет перезаписывать другие данные, структуры управления вашего распределителя или даже код (в определенных операционных средах).
Вот почему ошибки, связанные с указателями, могут быть настолько сложными для отслеживания . Представьте, что эти строки погребены глубоко в тысячах строк сложного кода, написанного кем-то другим, и вы должны пройти через них.
Тем не менее , программавсе равно должна быть скомпилирована, поскольку она остается совершенно корректной и стандартной в соответствии с C.
Такие ошибки, нет стандартных и нет компилятора не могут защитить неосторожные против. Я предполагаю, что это именно то, что они собираются научить вас.
Paranoid люди постоянно стремятся изменить на природу в C , чтобы избавиться от этих проблемных возможностей и так спасти нас от самих себя; но это неискренне . Это обязанность, которую мы обязаны принять, когда мы решаем использовать власть и получить свободу, которую нам предлагает более прямой и всеобъемлющий контроль над машиной. Промоутеры и приверженцы совершенства в исполнении никогда не примут ничего меньшего.
Переносимость и общность, которую он представляет, является принципиально отдельным соображением, и все, что стандарт стремится решить:
Вот почему совершенно правильно отличать его от определения и технической спецификации самого языка. Вопреки тому , что многие , похоже, считают Общностью является антитезой к исключительным и образцовым .
Заключить:
Если бы это было не так, программирование в том виде, в котором мы его знаем - и нам это нравится - было бы невозможно.
источник
3.4.3
это также раздел, на который вам следует обратить внимание: он определяет UB как поведение, «к которому настоящий международный стандарт не предъявляет никаких требований».C11 6.5.6/9
, помня, что слово «должен» указывает на требование «Когда вычитаются два указателя, оба должны указывать на элементы одного и того же объекта массива или один после последнего элемент массива объекта ".Указатели - это просто целые числа, как и все остальное в компьютере. Вы абсолютно можете сравнить их с
<
и>
и результатами производят , не вызывая к аварийному завершению программы. Тем не менее, стандарт не гарантирует, что эти результаты имеют какое-либо значение вне сравнения массивов.В вашем примере переменных, размещенных в стеке, компилятор может свободно размещать эти переменные в регистрах или адресах стековой памяти, и в любом порядке, который он выберет. Сравнения, такие как
<
и,>
следовательно, не будут одинаковыми для разных компиляторов или архитектур. Тем не менее,==
и!=
не столь ограничены, сравнение равенства указателей является допустимой и полезной операцией.источник
int x[10],y[10],*p;
, если код оцениваетy[0]
, затем оцениваетp>(x+5)
и записывает*p
без измененияp
в промежуточный период, и, наконец, оцениваетy[0]
снова, ...(ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')
его,isalpha()
потому что в какой-то здравой реализации эти символы будут прерывистыми? Суть заключается в том, что, даже если реализации вы знаете , не имеет проблемы, вы должны быть кодированием стандарта настолько , насколько это возможно , если вы цените мобильность. Я действительно ценю лейбл "Стандарты Мэйвен", хотя, спасибо за это. Я могу поставить в на мое резюме :-)