Как разные строки имеют одинаковый адрес
Я знаю, что для сравнения двух строк в C вам нужно использовать strcmp()
. Но я попытался сравнить две строки с оператором ==
, и это сработало. Я не знаю, как, потому что он просто сравнивает адрес двух строк. Он не должен работать, если строки разные. Но потом я напечатал адрес строк:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char* str1 = "First";
char* str2 = "Second";
char* str3 = "First";
printf("%p %p %p", str1, str2, str3);
return 0;
}
И результат был:
00403024 0040302A 00403024
Process returned 0 (0x0) execution time : 0.109 s
Press any key to continue.
Как возможно, что str1
и str3
имеют один и тот же адрес? Они могут содержать одну и ту же строку, но они не являются одной и той же переменной.
Ответы
Ответ 1
Нет никакой гарантии, что так будет всегда. В целом, разработчики поддерживают литеральный пул, поддерживающий каждый из строковых литералов только один раз, а затем для множественного использования строкового литерала используется тот же адрес. Но можно реализовать его по-другому - стандарт не создает ограничения на это.
Теперь ваш вопрос: вы смотрите на содержимое двух указателей, указывающих на один и тот же строковый литерал. Тот же строковый литерал дал одно и то же значение (они затухали в указатель на первый элемент). Но этот адрес тот же из-за причины, указанной в первом абзаце.
Кроме того, я хотел бы подчеркнуть предоставление аргумента спецификатора формата %p
(void*)
.
Ответ 2
Здесь есть интересный момент. На самом деле у вас на самом деле всего 3 указателя, указывающих на константные литерные строки. Поэтому компилятор может создавать одну str3
для "First"
и иметь там как str1
и str3
.
Это будет совершенно другой случай:
char str1[] = "First";
char str2[] = "Second";
char str3[] = "First";
Я объявил 3 разных массива символов, инициализированных из строк. Проверьте его, и вы увидите, что компилятор назначил разные адреса для трех разных строк.
Что вы должны помнить из этого: указатели и массивы - это разные животные, даже если массивы могут распадаться на указатели (подробнее об этом в этом сообщении из C FAQ)
Ответ 3
Когда конкретный строковый литерал появляется несколько раз в исходном файле, компилятор может выбрать, чтобы все экземпляры этой литеральной точки совпадали с одним и тем же местом.
В разделе 6.4.5 стандарта C, который описывает струнные литералы, указано следующее:
7 Не определено, являются ли эти массивы различными, если их элементы имеют соответствующие значения. Если программа пытается изменить такой массив, поведение не определено.
Если "неопределенное поведение" определено в разделе 3.4.4 как:
использование неопределенного значения или другое поведение, когда настоящий международный стандарт предоставляет две или более возможности и не налагает никаких дополнительных требований, которые выбираются в любом случае
В вашем случае строковый литерал "First"
появляется дважды в источнике. Поэтому компилятор использует тот же экземпляр литерала для обоих, в результате чего str1
и str3
указывают на один и тот же экземпляр.
Как указано выше, такое поведение не гарантируется. Два экземпляра "First"
могут отличаться друг от друга, в результате чего str1
и str3
указывают на разные места. Не указано ли два одинаковых экземпляра строкового литерала в одном и том же месте.
Ответ 4
Строковые литералы, подобно C99+ составным литералам, могут объединяться. Это означает, что два разных события в исходном коде могут фактически привести только к одному экземпляру в запущенной программе.
Это может быть даже в случае, если ваша цель не поддерживает аппаратную защиту от записи.
Ответ 5
Причина, по которой это так озадачивает, может быть: "Но что произойдет, если я установлю str1[1] = 'u';
;?" Поскольку его реализация определена ли str1 == str3
(и является ли адрес буквального "world!"
Адресом "hello, world!"
Плюс 7), делает ли это str3
превращение str3
в немецкого принца?
Ответ: может быть. Или, может быть, он только меняет str1
, или, может быть, он молча или не может измениться, или, может быть, он сбой программы, потому что вы написали в память только для чтения, или, может быть, она вызывает некоторые другие тонкие ошибки, поскольку она повторно использовала эти байты для еще одной цели, или что-то еще.
Тот факт, что вы даже можете назначить строковый литерал для char*
вообще, вместо того, чтобы использовать const char*
, в основном является рывком ради многолетнего устаревшего кода. Первые версии C не имели const
. Некоторые существующие компиляторы позволяют программам изменять строковые константы, а некоторые - нет. Когда комитет по стандартизации решил добавить ключевое слово const
из C++ в C, они не захотели сломать весь этот код, поэтому они предоставили компиляторам право делать в основном что-либо, когда программа меняет строковый литерал.
Практическое значение этого: никогда не присваивать строковый литерал char*
который не является const
. И никогда не предполагайте, что строковые константы выполняют или не перекрываются (если вы не гарантируете это с restrict
). Этот тип кода устарел с 1989 года и позволяет вам стрелять в ногу. Если вы хотите, чтобы указатель на строковый литерал (который мог или не мог совместно использовать память с другими константами), сохраните его в const char*
или, еще лучше, const char* const
. Это предупреждает вас, если вы попытаетесь изменить его. Если вам нужен массив char
который может быть изменен (и гарантированно не должен быть псевдоним любой другой переменной), сохраните его в char[]
.
Если вы считаете, что хотите сравнить строки по своим адресам, то вы действительно хотите либо хеш-значение, либо уникальный дескриптор.
Ответ 6
Чтобы добавить к другим ответам: это метод, называемый интерпретатором строк, где компилятор понимает, что строки одинаковы и поэтому хранит их только один раз. Java тоже имеет тенденцию делать это (хотя, как упоминалось другим плакатом, он зависит от компилятора).
Ответ 7
Это потому, что каждая строка с жестким кодом, подобная "First" и "Second", присутствует в части "только для чтения" исполняемого файла, поэтому у них есть адрес.
В linux вы можете увидеть их, используя "objdump -s -j.rodata execfile".
Если вы попытаетесь отобразить str1, str2 и str3-адрес, вы увидите, что есть разные.