Я столкнулся с этим, когда разыменование адреса памяти a-3 дает мне символ, присутствующий в b.
char *b = "welcome";
{
char *a = "home";
printf("%c", *(a-3));
}
Я не смог найти какого-либо относительного стандарта, подтверждающего это.
Я попытался скомпилировать это с помощью онлайн-компилятора C. Он вернул символ, хранящийся в b.
Я хотел понять, как такое исполнение возможно.
Это undefined behavior, потому что вы просили a[-3], который случайно является каким-то персонажем в b
Экспериментируя, нельзя понять, что разрешено, а что нет в C, приходится читать стандарт (или хорошую книгу).
halfblood_prince, Даже если бы в смежной памяти были строковые литералы "bbbbb" непосредственно перед "home" и "bbbbb" сразу после того же самого "home", printf("%c", *(a-3)); все равно не выдал бы "b", поскольку доступ через *(a-3) является неопределенным поведением.





не смог найти какого-либо относительного стандарта, поддерживающего это
Это потому, что его нет. Программа имеет неопределенное поведение, поскольку вы получаете доступ к "home" за пределами границ, а это означает, что программа может делать практически все, включая то, что она делала в вашем случае.
Никогда не полагайтесь на неопределенное поведение.
Программа имеет неопределенное поведение, поскольку вы пытаетесь получить доступ к области за пределами «дома», разыменовывая адреса памяти.
Однако, чтобы ответить на ваш вопрос о том, как возможно выполнение, компилятор может сохранять в памяти строку символов home\0 сразу после строки символов welcome\0.
Поскольку адрес, на который указывает указатель, является первым символом строки, a будет указывать на h в памяти. Если вы вычтете 3 из адреса, на который указывает a, вы вернетесь на 3 байта назад в памяти, передав нулевой символ в конце «приветствия», передав e и перейдя на m в приветствии в качестве символа для печати.
Тем не менее, как уже упоминалось в другом ответе, это неопределенное поведение, и на него никогда не следует полагаться, поскольку вы получаете доступ к a за пределами строки.
Хотел понять, как такое исполнение возможно
Программа имеет неопределенное поведение, поскольку доступ к памяти за пределами массива, на который указывает указатель a, недействителен: поведение неопределенно, выполнение может быть возможным без видимых побочных эффектов или завершиться неудачей с явным сообщением об ошибке, программа может напечатать символ, соответствующий вашему предположение или что-то другое, или быть внезапно прекращено, или перейти в бесконечный цикл, или что-то еще... включая различное поведение для разных запусков. Ничего нельзя ожидать от неопределенного поведения.
Стандарт не дает особых гарантий относительно адресов строковых литералов в памяти: они должны быть разными для разного содержимого строки, но относительный порядок не определен, на самом деле относительный порядок может вообще не иметь смысла, поскольку указатели на разные массивы можно сравнивать только на равенство, а не на относительный порядок с <, <=, > или >=.
Разные экземпляры одних и тех же строковых литералов могут иметь один и тот же адрес или разные адреса, в этом отношении также нет никакой гарантии.
Строковые литералы могут даже перекрываться: "hello world" и "world" можно разместить в памяти так, что "hello world" + 6 == "world". Ранние компиляторы C использовали это преимущество для уменьшения объема памяти. Современные компиляторы и компоновщики все еще могут это сделать или нет, без гарантий.
Поведение вашей программы неопределенное, но есть портативный способ проверить эти условия, используя printf:
#include <stdio.h>
int main(void) {
const char *a = "home";
const char *b = "welcome";
const char *c = "come";
const char *d = "home";
printf("%p: %s\n", (void *)a, a);
printf("%p: %s\n", (void *)b, b);
printf("%p: %s\n", (void *)c, c);
printf("%p: %s\n", (void *)d, d);
return 0;
}
Вывод покажет:
a и d, одинаковыми в памяти или разными."come" суффиксом строки "welcome" или отдельной строковой константой.Попробуйте эту программу несколько раз, используя один и тот же или разные компиляторы, на одной и той же или разных целях... Результаты могут отличаться даже при повторном запуске одного и того же исполняемого файла на одной и той же машине. Никаких гарантий.
Нет, они не должны быть такими. Кроме того, компилятору разрешено перекрывать строковые литералы, если один из них является суффиксом другого, поэтому нельзя предполагать, что они будут иметь какое-то относительное положение. Кстати, компилятор не обязан размещать строковые литералы подряд, могут быть принудительно применены некоторые альгинменты (введение пробелов), они могут быть расположены в обратном порядке исходной позиции (поэтому они не должны появляться сразу один за другим или в том порядке, в котором они появляются). в исходном коде), или они могут быть расположены в зависимости от других факторов (например, компилятор может позволить вам изменять строковые литералы, запуская неперекрытие их в памяти на основе использования, наблюдаемого в сгенерированном коде, это всего лишь идея, не говоря уже о том, что это используется любым компилятором)
Кроме того, строковые литералы, поступающие из модуля компиляции, могут располагаться в разных разделах конечного исполняемого файла, поэтому позиции в памяти полностью зависят от того, как их обрабатывает компоновщик.
Хотел понять, как такое исполнение возможно
Выполнение вашего кода невозможно, поскольку он не компилируется. Но в любом случае выражение, которое вы используете (*(a-3)), на самом деле эквивалентно a[-3] или даже (-3)[a] --- поскольку последнее является устаревшей конструкцией, которая лишь утверждает конмутативность сложения, но датируется эпохой первых компиляторов, поэтому, пожалуйста, не используйте *(a-3), когда хотите сказать a[-3]), указывает на три места из массива строковых литералов. Если вы исправите код, чтобы его можно было компилировать, выполнение по-прежнему возможно, поскольку в C проверка границ массива не выполняется (по соображениям эффективности), и именно это вызывает неопределенное поведение. Неопределенное поведение означает, что исполняемый код может быть создан, но результаты выполнения совершенно непредсказуемы.
Поскольку синтаксис кода неверен, я предполагаю (возможно, ошибочно), что оба строковых литерала находятся в одной и той же единице компиляции. Таким образом, компилятор может перекрывать два строковых литерала только в том случае, если один из них является суффиксом другого (а это не так), поэтому эти строковые литералы никогда не будут перекрываться.
Я хотел понять, как такое исполнение возможно.
Есть как минимум три способа подумать об этом:
Другой вопрос, где вы «наткнулись» на этот код и какой урок пытался преподать его автор? Надеемся, что код не был представлен как пример хорошего кода, который вы хотели бы подражать в своей работе. Вероятно, это было подано как диковинка, из которой, по мнению автора, можно чему-то поучиться. (А потом, будет ли урок, который вы можете извлечь, ценным, это уже другая история.)
Это был просто пост сообщества с канала Elearning на YouTube, я думаю, он был опубликован, чтобы возбудить любопытство.
Несвязано:
*(a-3)написано более четкоa[-3]