C / C ++: оптимизация указателей на строковые константы - PullRequest
14 голосов
/ 27 марта 2009

Посмотрите на этот код:

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

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

Ответы [ 6 ]

30 голосов
/ 27 марта 2009

На это нельзя положиться, это оптимизация, которая не является частью какого-либо стандарта.

Я изменил соответствующие строки вашего кода на:

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) повторно использовал первую строку во всех последующих подстроках. Это выбор компилятора, как расположить строковые константы в памяти.

16 голосов
/ 27 марта 2009

Это чрезвычайно простая оптимизация, возможно, настолько, что большинство авторов компиляторов даже не считают ее вообще оптимизацией. Установка флага оптимизации на самый низкий уровень, в конце концов, не означает «быть полностью наивным».

Компиляторы будут различаться в зависимости от того, насколько агрессивны они при слиянии повторяющихся строковых литералов. Они могут ограничиться одной подпрограммой - поместите эти четыре объявления в разные функции вместо одной функции, и вы можете увидеть разные результаты. Другие могут сделать весь блок компиляции. Другие могут полагаться на компоновщик для дальнейшего объединения нескольких модулей компиляции.

Вы не можете полагаться на это поведение, если в документации вашего конкретного компилятора не указано, что вы можете. Сам язык не предъявляет никаких требований в этом отношении. Я бы с осторожностью полагался на это в своем собственном коде, даже если переносимость не была проблемой, потому что поведение может изменяться даже между различными версиями компилятора одного поставщика.

12 голосов
/ 27 марта 2009

Вы, конечно, не должны полагаться на такое поведение, но большинство компиляторов будут делать это. Любое литеральное значение («Hello», 42 и т. Д.) Будет сохранено один раз, и любые указатели на него будут естественным образом преобразовываться в эту единственную ссылку.

Если вы обнаружите, что вам нужно полагаться на это, будьте в безопасности и перекодируйте его следующим образом:

char *watchmen = "Watchmen";
char *foo = watchmen;
char *bar = watchmen;
9 голосов
/ 27 марта 2009

Вы не должны рассчитывать на это, конечно. Оптимизатор может сделать с вами что-то непростое, и ему следует разрешить это.

Это однако очень распространено. Я помню, в далеком 87 году одноклассник использовал компилятор DEC C и обнаружил странную ошибку, из-за которой все его буквальные 3 превратились в 11 (числа могли измениться, чтобы защитить невинных). Он даже сделал printf ("%d\n", 3) и напечатал 11.

Он позвал меня, потому что это было так странно (почему это заставляет людей думать обо мне?), И примерно через 30 минут царапин на голове мы нашли причину. Это была строка примерно так:

if (3 = x) break;

Обратите внимание на один символ "=". Да, это была опечатка. У компилятора была маленькая ошибка, и он допустил это. В результате все его буквальные 3 во всей программе превратились в то, что было в х в то время.

В любом случае ясно, что компилятор C помещал все литералы 3 в одно и то же место. Если компилятор C в 80-х годах был способен сделать это, это не может быть слишком сложно сделать. Я ожидаю, что это будет очень распространенным явлением.

5 голосов
/ 27 марта 2009

Я бы не стал полагаться на такое поведение, потому что сомневаюсь, что стандарты C или C ++ явно указали бы на такое поведение, но имеет смысл, что компилятор это делает. Также имеет смысл, что он демонстрирует такое поведение даже в отсутствие какой-либо оптимизации, указанной для компилятора; в этом нет компромисса.

Все строковые литералы в C или C ++ (например, «строковый литерал») доступны только для чтения и, следовательно, являются постоянными. Когда вы говорите:

char *s = "literal";

Вы в некотором смысле понижаете строку до неконстантного типа. Тем не менее, вы не можете покончить с атрибутом строки, доступным только для чтения: если вы попытаетесь манипулировать им, вы попадете во время выполнения, а не во время компиляции. (На самом деле это хорошая причина использовать const char * при назначении строковых литералов вашей переменной.)

2 голосов
/ 27 марта 2009

Нет, на это нельзя положиться, но хранение строковых констант только для чтения в пуле - довольно простая и эффективная оптимизация. Это просто вопрос сохранения алфавитного списка строк, а затем их вывода в конце в объектный файл. Подумайте, сколько «\ n» или «» констант в средней базе кода.

Если компилятор хочет получить дополнительную фантазию, он также может повторно использовать суффиксы: «\ n» можно представить, указав на последний символ «Hello \ n». Но это, вероятно, дает очень мало пользы для значительного увеличения сложности.

Во всяком случае, я не верю, что стандарт говорит о том, где что-то хранится на самом деле. Это будет очень специфичная для реализации вещь. Если вы поместите два из этих объявлений в отдельный файл .cpp, то, скорее всего, все тоже изменится (если ваш компилятор не выполнит значительную работу по связыванию.)

...