Как разные строки имеют одинаковый адрес

Я знаю, что для сравнения двух строк в 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-адрес, вы увидите, что есть разные.