Подтвердить что ты не робот

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).

Неужели компилятор всегда так оптимизирует, что ищет все строковые константы, чтобы убедиться, что они равны? Можно ли полагаться на это поведение?

4b9b3361

Ответ 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 файл, то, скорее всего, это тоже изменится (если ваш компилятор не выполняет значительную работу ссылок).