Числовые значения адресов, хранящихся в s1
, s2
и s3
(то есть адреса массивов символов, инициализированных в "hi"
, "Bye"
и "Out"
), гарантированно не равны, и они, вероятно, будут близки друг к другу, но не требуется, чтобы они были смежными или в каком-либо конкретном порядке. Аналогично, числовые значения адресов &s1
, &s2
и &s3
(то есть адреса локальных переменных s1
, s2
и s3
) гарантированно не равны, и могут быть расположены близко друг к другу, но не обязательно должны быть смежными или в каком-либо определенном порядке.
s1
, s2
и s3
содержат адреса строковых литералов. Строковые литералы являются синтаксическим сахаром для постоянных массивов со статической продолжительностью хранения, что означает, что все они выделяются и инициализируются до запуска вашей программы. Поскольку они не являются массивом того же со статической продолжительностью хранения, 1 они не могут иметь один и тот же адрес, но, кроме того, технически они могут находиться где угодно в ОЗУ. На практике они будут помещены в область ОЗУ, выделенную для данных только для чтения, что означает, что они будут находиться рядом друг с другом, но компилятор и компоновщик размещают эту область так, как им удобно, и вы не должны полагаться на то, как они это сделали.
&s1
, &s2
и &s3
- адреса локальных переменных. Опять же, поскольку они не являются одной и той же переменной, они не могут иметь один и тот же адрес, и, опять же, они, вероятно, будут близки друг к другу, поскольку все они принадлежат одной и той же функции, но, опять же, точные отношения их адресов в памяти зависит от компилятора, и вы не должны полагаться на него. Люди, знакомые с языком ассемблера, часто думают, что локальные переменные будут помещаться в аппаратный стек по одной за раз, поэтому они должны быть последовательными в соответствии с «направлением роста стека» 2 , но скомпилированный код не не делай так. Компилятору удобнее перемещать указатель стека один раз при входе в каждую функцию, а затем оставлять его там, создавая объект, называемый "кадр стека". Это означает, что локальные переменные эффективно возникают сразу, и их адреса в пределах стекового фрейма могут быть самыми удобными для компилятора. (Например, они могут быть отсортированы по размеру, чтобы наименьшие переменные находились ближе всего к указателю стека и могли быть доступны с более короткими смещениями.)
1 В стандарте C есть особый случай, который позволяет "дедуплицировать" строки, т.е. если вы пишете const char *a = "foo";
в верхней части одной функции и const char *b = "foo";
в верхней части другой адреса, сохраненные в a
и b
, могут совпадать. Но ваши строки не идентичны, поэтому с ними нельзя обращаться.
2 Забавный факт: для реализации на C вообще не требуется аппаратный стек, не говоря уже о том, чтобы расти в определенном направлении! Требуется поддерживать рекурсивные вызовы функций, но как это сделать, совершенно не определено.