C/С++: оптимизация указателей на строковые константы
Посмотрите на этот код:
#include <iostream>
using namespace std;
int main()
{
const char* str0 = "Watchmen";
const char* str1 = "Watchmen";
char* str2 = "Watchmen";
char* str3 = "Watchmen";
cerr << static_cast<void*>( const_cast<char*>( str0 ) ) << endl;
cerr << static_cast<void*>( const_cast<char*>( str1 ) ) << endl;
cerr << static_cast<void*>( str2 ) << endl;
cerr << static_cast<void*>( str3 ) << endl;
return 0;
}
Что производит такой вывод:
0x443000
0x443000
0x443000
0x443000
Это было в компиляторе g++ под Cygwin. Указатели указывают на одно и то же местоположение, даже если оптимизация не включена (-O0
).
Неужели компилятор всегда так оптимизирует, что ищет все строковые константы, чтобы убедиться, что они равны? Можно ли полагаться на это поведение?
Ответы
Ответ 1
Это чрезвычайно простая оптимизация, возможно, настолько, что большинство авторов компилятора даже не считают, что это большая часть оптимизации. Установка флага оптимизации на самый низкий уровень не означает "Будьте полностью наивны", в конце концов.
Составители будут отличаться тем, насколько агрессивны они при объединении повторяющихся строковых литералов. Они могут ограничиться одной подпрограммой - поместить эти четыре объявления в разные функции вместо одной функции, и вы можете увидеть разные результаты. Другие могут выполнять целую компиляцию. Другие могут полагаться на компоновщик для дальнейшего объединения нескольких блоков компиляции.
Вы не можете полагаться на это поведение, если только в вашей конкретной документации для компилятора вы не можете. Сам язык не предъявляет никаких требований в этом отношении. Я был бы осторожен в том, чтобы полагаться на это в своем собственном коде, даже если переносимость не была проблемой, потому что поведение может изменяться даже между разными версиями одного поставщика компилятора.
Ответ 2
На него нельзя положиться, это оптимизация, которая не является частью какого-либо стандарта.
Я изменил соответствующие строки вашего кода на:
const char* str0 = "Watchmen";
const char* str1 = "atchmen";
char* str2 = "tchmen";
char* str3 = "chmen";
Выход для уровня оптимизации -O0:
0x8048830
0x8048839
0x8048841
0x8048848
Но для -O1 это:
0x80487c0
0x80487c1
0x80487c2
0x80487c3
Как вы можете видеть GCC (v4.1.2) повторно используемую первую строку во всех последующих подстроках. Выбор компилятора, как упорядочить строковые константы в памяти.
Ответ 3
Вы, конечно же, не должны полагаться на это поведение, но большинство компиляторов сделают это. Любое буквальное значение ( "Hello", 42 и т.д.) Будет сохранено один раз, и любые указатели на него будут естественным образом разрешены к этой единственной ссылке.
Если вы обнаружите, что вам нужно полагаться на это, тогда будьте безопасны и перекодируйте следующим образом:
char *watchmen = "Watchmen";
char *foo = watchmen;
char *bar = watchmen;
Ответ 4
Вы не должны рассчитывать на это, конечно. Оптимизатор может сделать что-то сложное на вас, и ему должно быть позволено это сделать.
Это, однако, очень распространено. Я помню еще в 1987 году, когда одноклассник использовал компилятор DEC C и имел эту странную ошибку, где все его буквальные 3 получили превращение в 11 (номера, возможно, изменились для защиты невинных). Он даже сделал printf ("%d\n", 3)
и напечатал 11.
Он позвонил мне, потому что это было так странно (почему это заставляет людей думать обо мне?), и примерно через 30 минут царапин на голове мы нашли причину. Это была строка примерно так:
if (3 = x) break;
Обратите внимание на одиночный символ "=". Да, это была опечатка. У компилятора была небольшая ошибка, и это разрешило. Эффект заключался в том, чтобы превратить все его буквальные 3 во всю программу во все, что попадало в х в то время.
В любом случае, ясно, что компилятор C помещал все буквальные 3 в одно и то же место. Если компилятор C в 80 году мог это сделать, это не может быть слишком сложно сделать. Я ожидаю, что это будет очень распространено.
Ответ 5
Я бы не стал полагаться на поведение, потому что я сомневаюсь, что стандарты C или С++ будут делать это явно, но имеет смысл, что это делает компилятор. Также имеет смысл, что оно проявляет это поведение даже в отсутствие какой-либо оптимизации, указанной для компилятора; в нем нет компромиссов.
Все строковые литералы на C или С++ (например, "строковый литерал" ) доступны только для чтения и, следовательно, постоянны. Когда вы говорите:
char *s = "literal";
Вы в некотором смысле преуменьшаете строку неконстантным типом. Тем не менее, вы не можете покончить с атрибутом read-only строки: если вы попытаетесь манипулировать им, вы будете пойманы во время выполнения, а не во время компиляции. (На самом деле это хорошая причина использовать const char *
при назначении строковых литералов для вашей переменной.)
Ответ 6
Нет, на него нельзя положиться, но сохранение констант строки только для чтения в пуле - довольно простая и эффективная оптимизация. Это просто вопрос хранения алфавитного списка строк, а затем вывод их в файл объекта в конце. Подумайте, сколько "\n" или "" констант находится в средней базе кода.
Если компилятор хотел получить дополнительную причудливость, он мог бы снова использовать суффиксы: "\n" можно представить, указав на последний символ "Hello\n". Но это, вероятно, будет очень небольшим, чтобы значительно увеличить сложность.
Во всяком случае, я не верю, что в стандарте говорится о том, где что-то действительно хранится. Это будет очень специфичным для реализации. Если вы поместите две из этих деклараций в отдельный .cpp файл, то, скорее всего, это тоже изменится (если ваш компилятор не выполняет значительную работу ссылок).