LINUX.ORG.RU

Защита от дурака в gcc?

 ,


0

2

По мотивам срача, где stevejobs предложил пример того, почему const не даёт никаких гарантий.

Я заметил такое поведение у себя:

#include <cstdio>
#include <cassert>

int main() {
    const int three = 3;
    printf( "three=%d\n", three );

    int *ptr;
    ptr = (int *)( &three );
    *ptr = 5;

    printf( "three=%d\n", three);
    assert(three == 3);

}

Выхлоп:

$ g++-4.7 -Wall -Wextra -O0 test.cpp -o test
$ ./test
three=3
three=3

При этом в отладчике:

(gdb) b 8
Breakpoint 1 at 0x4006ff: file test.cpp, line 8.
(gdb) r
Starting program: /home/del/test/const/test 
three=3

Breakpoint 1, main () at test.cpp:9
9	    ptr = (int *)( &three );
(gdb) p three
$1 = 3
(gdb) p &three
$2 = (const int *) 0x7fffffffe2d4
(gdb) n
10	    *ptr = 5;
(gdb) p ptr
$3 = (int *) 0x7fffffffe2d4
(gdb) n
12	    printf( "three=%d\n", three);
(gdb) p *ptr
$4 = 5
(gdb) p three
$5 = 5
(gdb) n
three=3

Что собственно происходит? Почему (судя по gdb) по адресу 0x7fffffffe2d4 лежит 5, и three == 5, но printf выводит 3, и не срабатывает assert() ?

Если убрать 'const', то всё нормально и никаких аномалий нету.

Тестировал на gcc 4.2, 4.6 и 4.7.

PS вообще C-style cast'ы зло, static_cast<> тут законно ругнётся.

☆☆☆☆☆

значит компилятор вообще не лезет в память и хранит тройку в регистре, либо вообще использует 3 как константу

wota ★★
()

PS вообще C-style cast'ы зло, static_cast<> тут законно ругнётся.

а то ты не видишь что он у тебя const. С таким же успехом мог бы накосячить с const_cast

mashina ★★★★★
()

По мотивам срача, где stevejobs предложил пример того, почему const не даёт никаких гарантий.

Это был не пример того, что const не дает никаких гарантий, это был пример непонимания товарищем stevejobs для чего вообще нужен const в C++. Он видимо с Java слез и ожидал защиты, которую дает VM.

m0rph ★★★★★
()

Ну так ты же пообещал компилятору three меняться не будет? Вот он и мало-мало соптимизировал printf.

KblCb ★★★★★
()

Константы в C++ - это константы времени компиляции. В отличие от си, где выхлоп будет другим.

unsigned ★★★★
()
Ответ на: комментарий от unsigned

Константы в C++ - это константы времени компиляции.

Не согласен, от константы времени компиляции нельзя адрес взять.

AF ★★★
()
Последнее исправление: AF (всего исправлений: 1)
Ответ на: комментарий от unsigned

Если это константа времени компиляции, то почему от неё можно взять адрес, и куда он тогда записал пятёрку?

Gvidon ★★★★
()
Ответ на: комментарий от AF

Поэтому в с++11 и добавили constexpr, чтобы отделить флажок «TODO: не менять в данной области видимости»(const) от того, что вы сказали

wingear ★★★★
()
Последнее исправление: wingear (всего исправлений: 1)

const int three = 3;

ptr = (int *)( &three ); *ptr = 5;

Что собственно происходит?

хз. ну или не по нашему: UB. А что ты хотел от такого быдлокода? Это типичное деление на ноль: сначала ты скзал, что three это константа, а потом константу изменил.

А она — не изменилась. Чего тут удивляться? Что const оказался «сильнее»? Ну бывает. С другими опциями и/или на других платформах «сильнее» будет оператор «равно». ЯХЗ.

emulek
()
Ответ на: комментарий от AF

от константы времени компиляции нельзя адрес взять

Зато её можно подставить в printf. А раз берется адрес - она ещё и в память помещается.

unsigned ★★★★
()
Последнее исправление: unsigned (всего исправлений: 1)
Ответ на: комментарий от Gvidon

Где можно подставить константу - компилятор это сделает. Где нельзя (как при взятии адреса) - будет работать как с переменной.

Почему от константы вообще разрешено брать адрес - видимо, наследие си.

unsigned ★★★★
()
Ответ на: комментарий от unsigned

А раз берется адрес - она ещё и в память помещается.

Значит, это не константа времени компиляции )

AF ★★★
()
Ответ на: комментарий от AF

В этих ваших плюсах всё перемешано. Она работает и во время компиляции (подставляется), и во время выполнения (как переменная).

Причем если компилятору удастся подставить её во всех случаях - он может и не помещать её в память.

unsigned ★★★★
()
Последнее исправление: unsigned (всего исправлений: 1)
Ответ на: комментарий от unsigned

Почему от константы вообще разрешено брать адрес

а как ты большие константы будешь в функции/методы передавать? По значению? Дык это долго. Вот и передают либо константную ссылку, либо указатель на константу (не путать с константным указателем!).

Да, для некоторых говнокодеров тут тоже открывается отличная возможность ещё раз выстрелить в свои яйца(в то место, где они когда-то были).

emulek
()
Ответ на: комментарий от unsigned

Причем если компилятору удастся подставить её во всех случаях - он может и не помещать её в память.

Если бы все так и было, то от такой переменной нельзя было бы брать адрес. Вообще нельзя! При попытки взятия адреса компилер должен выдавать ошибку.

AF ★★★
()
Последнее исправление: AF (всего исправлений: 1)
Ответ на: комментарий от unsigned

В этих ваших плюсах всё перемешано.

это не баг, а фича: Если на целевой платформе выгоднее хранить константу в памяти/регистре, она хранится в памяти/регистре. Если не выгодно, она хранится прямо в коде, как константа. Выбирает это компилятор. Часто это даже от значения константы зависит, ибо например в x86 часто выгодно не хранить константу 0, а создавать её прямо в runtime'е, например вычитая аккумулятор из аккумулятора, это быстрее и короче, чем хранить в коде(в данных) этот ноль, и оттуда его подгружать.

emulek
()
Ответ на: комментарий от AF

Это как раз случай, когда подстановка невозможна. Поэтому она попала в память, но в printf она не считывается из этой памяти, а подставляется.

unsigned ★★★★
()
Ответ на: комментарий от AF

Причем если компилятору удастся подставить её во всех случаях - он может и не помещать её в память.

Если бы все так и было, то от такой переменной нельзя было бы брать адрес. Вообще нельзя!

нельзя.

При попытки взятия адреса компилер должен выдавать ошибку.

компилятор не обязан выдавать ошибку на любой говнокод. Он не имеет модуль AI, который умнее программиста. Только программист решает, что можно, а что нельзя.

emulek
()
Ответ на: комментарий от unsigned

В программе, которая состоит из многих файлов, не возможно отследить все попытки взятия адреса от константы, т к эти попытки могут быть живописно разбросаны по огромному числу файлов.
В принципе, оптимизатор в линковщике может догадаться, что обращений к константе в памяти нету и выкинет константу из памяти.
Но кивать на оптимизатор тоже как-то не правильно. Оптимизатор вообще должен по возможности выкидывать неиспользуемый код и неиспользуемый переменные. А это вовсе не значит, что есть переменные, которые в памяти вообще не хранятся.
Так что константная переменная - это переменная, которую нельзя явно изменять + обращения к которой компилятор/линковщик могут специальным образом соптимизировать.

AF ★★★
()
Ответ на: комментарий от emulek

компилятор не обязан выдавать ошибку на любой говнокод.

Компилятор обязан выдавать ошибку на некорректный код. Если операция взятия адреса не приводит к ошибке, то это значит:

1) Код с точки зрения компилятора корректен
2) переменная таки каким то образом присутствует в памяти, и, сдедовательно, получен адрес на эту память

Если че, я ни разу не против существующего поведения.

AF ★★★
()
Ответ на: комментарий от unsigned

Почему от константы вообще разрешено брать адрес - видимо, наследие си.

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

Си сложный, нетривиальный во многих местах, но отлично документированный. Кто привык «интуитивно понимать» происходящее, или ловить исключение и только потом читать кусок мануала, дуйте в свою яву, пожалуйста, и не задавайте глупых вопросов ;)

arturpub ★★
()
Ответ на: комментарий от AF

Компилятор обязан выдавать ошибку на некорректный код.

ну ты слышал вопрос: «сколь вешать в битах?» Если у тебя константная структура в 8192 байта, то ясное дело, что компилятор её в регистры не запихает, и в коде хранить не станет. Станет хранить в памяти, хоть оно и константное. А значит — можно взять адрес. И не только можно, но и нужно, ибо передать в функцию 8 байт указателя куда как быстрее, чем 8192 байта константной структуры.

А вот если у тебя int константа, то брать её адрес просто глупо. Ибо адрес сейчас обычно в восемь байт, а вот int всего 4 байта.

Но это всё конечно от платформы зависит. И жёстко НЕ прибивается (если жёстко сказать «нельзя брать адрес», то будет тормозить на больших структурах).

Если че, я ни разу не против существующего поведения.

а поведение и не определено. Тут у программиста есть некоторая свобода выбора. Он может брать адрес константы, но при этом обязан помнить, что компилятор _может_ соптимизировать эти константы так, что их «адрес» потеряет всякий смысл.

emulek
()
Ответ на: комментарий от anonymous

Структуры или массивы, например.

static const struct {
    const char *name;
    int (*func)(void *);
} lib[] = {
    { "read",  fn_read  },
    { "write", fn_write },
    { NULL, NULL },
};
arturpub ★★
()
Ответ на: комментарий от emulek

если жёстко сказать «нельзя брать адрес», то будет тормозить на больших структурах

Ну так имелось ввиду, что нельзя брать адрес явно! А у себя внутри компилятор может генерить, как посчитает нужным.

AF ★★★
()
Ответ на: комментарий от AF

Если бы все так и было, то от такой переменной нельзя было бы брать адрес.

Можно взять адрес у memcpy(). Но при этом вместо memcpy() компилятор вставляет intrinsic.

Absurd ★★★
()
Ответ на: комментарий от arturpub

Кто не осилил мануал и не знает, что const- и restrict-значения могут валяться в регистре и не синкаться с памятью, причем без проверок на дурака — вон из языка.

+1

вот пример говнокода:

x-- - --x;
Этот говнокод может быть вычислен тремя разными способами, и выдавать три разных результата. Gcc на x86 выдаёт самый быстрый результат, а именно константный ноль. А ошибок — нету. Где тут ошибка?

Дана задача, которую можно выполнить тремя способами, исполнитель выбрал самый быстрый и эффективный. В чём проблема?

emulek
()
Ответ на: комментарий от emulek

компилятор _может_ соптимизировать эти константы так, что их «адрес» потеряет всякий смысл.

Вообще-то нет. Есть два уровня: уровень абстракции языка и уровень компилятора. На уровне языка это все объекты в памяти, у которых гарантированно можно взять адрес (и для разных объектов он *всегда* будет разный). Компилятор же ограничен тем, что ты успел сделать. Если ты взял где-то адрес, и эта ветка не была optimized-out, то придется ему держать это в памяти. Если нет, то появляется свобода выбора — пихать в регистры и imm8/16/32, или оставить таки в .data.

Другое дело, что записью в const мы переходим границу дозволенного, и все оптимизирующие механизмы, не зная об этом, могут начать работать против нас.

arturpub ★★
()
Ответ на: комментарий от Absurd

Можно взять адрес у memcpy(). Но при этом вместо memcpy() компилятор вставляет intrinsic.

Не совсем удачный пример. Адрес memcpy наверняка будет указывать на библиотечную memcpy, и если по этому указателю ф-ю вызвать, то все сработает предсказуемо.

Гораздо веселее может быть с strlen от константной строки. Поскольку строка на момент компиляции известна, то компилер может сразу посчитать длину и подставлять ее, где надо. При этом не вызова strlen не intrinsic-strlen вообще нигде не будет.

AF ★★★
()
Ответ на: комментарий от AF

Ну так имелось ввиду, что нельзя брать адрес явно! А у себя внутри компилятор может генерить, как посчитает нужным.

дык если ты напишешь bar::f(struct S); дык оно тебе эту struct S побитно и скопирует. Как тут обойтись без указателя/ссылки? При чём не важно, где тут стоит (стоят) const'ы. Всё равно компилятор не догадается взять внутри себя адрес от структуры, даже если знает, что структура не меняется и не может изменится. Да и зачем это усложнение нужно? Gcc и так часами компиллит C++ код.

emulek
()
Ответ на: комментарий от AF

Если бы все так и было, то от такой переменной нельзя было бы брать адрес. Вообще нельзя!

Ты для начала посмотри в спеке языка, что написано о взятии адреса константы (константы, не литерала) и преобразовании этого адреса в не-константный.

tailgunner ★★★★★
()
Ответ на: комментарий от emulek

Чо они все никак не поймут, так это то, что любой «говнокод» является валидным и компилируется «как-то», причем не потому что так задумано, а потому что «говнокод» не является предусмотренным входом для компилятора. Это как обои в стиральной машине мыть — обоям херово, машине пофигу.

arturpub ★★
()

о боже, еще один крестосрач. када ж вас перебанят.

x0r ★★★★★
()
Ответ на: комментарий от arturpub

Если ты взял где-то адрес, и эта ветка не была optimized-out, то придется ему держать это в памяти.

угу. А это усложнение по твоему нужно? Если говнокодер идиот, и пишет говно вместо кода, то почему компилятор должен за этим следить?

Другое дело, что записью в const мы переходим границу дозволенного

чуть раньше: когда стивжопс скастовал (const int*) в (int*). Вот тогда он скатился до говнокода. Если-бы он попробовал писать в указатель на константу, то компилятор ему-бы помог. Но он САМ сказал, что «это теперь не константа!». И компилятор не обязан такое замечать и исправлять.

emulek
()
Ответ на: комментарий от arturpub

а потому что «говнокод» не является предусмотренным входом для компилятора.

не, оно кагбэ «предусмотрено».

Это как обои в стиральной машине мыть — обоям херово, машине пофигу.

не совсем удачный пример. Это скорее как кошка в микроволновке: в мануале ЯВНО написано, что производителю наплевать, что случится с вашей кошкой, если вы её засунете в эту микроволновку. А претензии вы в этом случае должны предъявлять папе с мамой. И в случае каста const в неконст — к ним же.

emulek
()
Ответ на: комментарий от emulek

А это усложнение по твоему нужно?

Ну по мне так сравнение адресов объектов полезная вещь. К тому же модульная система не позволяет такие оптимизации — однажды став глобальной, переменная доступна всем будущим неизвестным модулям. Причем заметь, они ее могут объявить как неконстантную, и для них она будет работать совершенно нормально :) Тут вопрос не как должно быть красиво, а как должно работать. Сейчас все по-простому, зачем это усложнять идеями(с)(тм)?

компилятор не обязан такое замечать и исправлять.

Конечно, открываешь дверь в подвал — готовься выскочит бяка.

arturpub ★★
()
Ответ на: комментарий от arturpub

Ну по мне так сравнение адресов объектов полезная вещь. К тому же модульная система не позволяет такие оптимизации — однажды став глобальной, переменная доступна всем будущим неизвестным модулям.

тут как с функциями: глобальная функция(открытый метод) просто обязаны лежать в памяти, и иметь возможность взятия адреса. Переменные в этом ни чем не отличаются. Т.е. если у тебя есть errno, то это кусок памяти, а не регистр. Даже если ты её никогда не меняешь.

Причем заметь, они ее могут объявить как неконстантную, и для них она будет работать совершенно нормально :) Тут вопрос не как должно быть красиво, а как должно работать. Сейчас все по-простому, зачем это усложнять идеями(с)(тм)?

ну если ты константу объявишь неконстантой, то это как брошенные на пороге грабли. В принципе — годная защита от воров (: Пока ты сам об этой защите помнишь конечно (:

Конечно, открываешь дверь в подвал — готовься выскочит бяка.

дети и не должны ходить в подвал (:

emulek
()
Ответ на: комментарий от AF

Страуструп, 5.4:

В зависимости от своего «интеллекта», конкретные компиляторы могут по-разному использовать факт константности объекта. Например, инициализатор константного объекта часто (но не всегда) является константным выражением (§С5); если это так, то его можно вычислить во время компиляции. Далее, если компилятор может выявить все случаи использования константы, то он может не выделять под нее память. Например:

const int cl = 1; 
const int c2 = 2; 
const int c3 = my_f(3) ; //значение сЗ не известно в момент компиляции 
extern const int c4; //значение с4 не известно в момент компиляции 
const int* p = &с2; // под с2 нужно выделить память
Так как компилятор знает значения переменных c1 и с2, он может использовать их в константных выражениях. Поскольку значения для сЗ и с4 во время компиляции неизвестны (исходя лишь из информации в данной единице компиляции; см. §9.1), для них требуется выделить память. Адрес переменной с2 вычисляется (и где-то, наверное, используется), и для нее память выделять тоже нужно. Самым простым и часто встречающимся случаем является ситуация, когда значение константы во время компиляции известно и память под нее не выделяется', c1 служит примером такого случая.

unsigned ★★★★
()
Ответ на: комментарий от emulek

намного больше, чем один указатель.

ты часто видишь такие константы? Никто их не мешает, кстати, засунуть в два/четыре регистра.

anonymous
()
Ответ на: комментарий от anonymous

намного больше, чем один указатель.

ты часто видишь такие константы?

я отвечал на утверждение, в котором было слово «всегда».

Никто их не мешает, кстати, засунуть в два/четыре регистра.

регистров в x86 не так уж и много. Это тоже ценный ресурс.

emulek
()
Ответ на: комментарий от emulek

Он не имеет модуль AI, который умнее программиста.

это как раз та ошибка, которая ловится статически

точнее, ловилась БЫ, если БЫ кресты не тянули за собой совместимость с говном мамонта

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

stevejobs ★★★★☆
()
Ответ на: комментарий от emulek

И компилятор не обязан такое замечать и исправлять.

он мог бы такое не компилировать вообще. То же касается и UB.

виноват тут совсем не компилятор, виновата ваша библия, стандарт тоесть

проблема в вашей сраной совместимости. Не хватит у них яиц сделать 2 разных конста: unsafe_const (старый, тот о котором речь, с сохранением «нулевой стоимости»), а const сделать алиасом на safe_const (который тратит в памяти дополнительный байт на отметку константности). Это же сломает столько софта, написанного через жопу людьми, которые Библию не читали. Не хватит им яиц чтобы выбросить из крестов Си — это же сломает столько кода, написанного 100500 лет назад. Так и будут сидеть с одним недостаточным яйцом и сосать лапу ближайшую вечность =)

stevejobs ★★★★☆
()
Ответ на: комментарий от stevejobs

«не мешать стрелять в ногу» - главная фича плюсов. Еще одна жава нахрен не нужна.

AptGet ★★★
()
Ответ на: комментарий от stevejobs

Так-то есть варианты -std=..., но почему-то из стандарта в стандарт ub не запрещают :)

safe_const (который тратит в памяти дополнительный байт на отметку константности)

Ты вот написал недавно const int i = 0; *(int *)&i = 1;, было? Было. Кто тебе мешает найти этот чертов «константый байт» и его изменить? Видишь ли, если нельзя на уровне среды что-то запретить, то и не надо это запрещать, достаточно оговорить последствия (либо указать, что они могут зависеть от — тогда говорят «это UB»). То же касается костыльной приватности в крестовых классах, ведь даже к impl можно получить доступ при желании. Другое дело виртуальные машины — там принципиально нельзя выбраться из песочницы, не используя внешние модули для ее эксплойта. Так оставим же разным уровням абстракции разные идеологии.

arturpub ★★
()
Ответ на: комментарий от arturpub

Кто тебе мешает найти этот чертов «константый байт» и его изменить?

в &/*/new/malloc/etc можно сделать механизм «скрытых блоков памяти», например. Так, что перед любым объектом управляемой памяти можно будет иметь кусок «скрытой» памяти. Это на уровне компилятора, в языке оно недоступно вообще никак. На основе скрытых префиксов сделать механизм добавления метаинформации, в т.ч. «настоящей» защиты памяти. А можно как-нибудь по-другому это сделать.

stevejobs ★★★★☆
()
Ответ на: комментарий от stevejobs

А зачем это в плюсах? Они не Java, последняя уже есть.

Суть в том, что программер должен знать, что делать. За это ему от языка достаётся одно: низкий оверхед.

intelfx ★★★★★
()
Ответ на: комментарий от intelfx

А зачем это в плюсах?

это фича. Не хочешь — не пользуйся. Т.к. возможность «не пользоваться» может привести к WTF, все такие операции должны находиться под прагмой или именованным блоком кода UNSAFE_MEMORY. В Java так нельзя. А еще в Java нельзя отключить GC, разве что с помощью Unsafe, но эта штука тупо неудобна, проще JNI.

(точнее, можно сделать препроцессор и писать на «java со вставками на си», чтобы си-вставки во время парсинга отлетали в отдельны файлы и подключались по JNI, но это сломает поддержку IDE, а без IDE я писать не готов. Разве что через MPS, для него вроде есть модель С++, но оно пока не очень удобно именно с точки зрения удобства редактирования не-текста и удобства интерфейса)

stevejobs ★★★★☆
()
Последнее исправление: stevejobs (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.