LINUX.ORG.RU

Вызов никогда не вызываемой функции

 ,


3

5

Ваши ставки, господа: насколько безопасно на своём компьютере запускать такую программу? Не сотрет ли она вам корень?

Люблю C++.

#include <cstdlib>

typedef int (*Function)();

static Function Do;

static int EraseAll() {
  return system("yes");
}

void NeverCalled() {
  Do = EraseAll;  
}

int main() {
  return Do();
}
★★★★☆

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

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

Топик стартер несколько раз намекнул, что сабж — проблема языка, я всё жду, когда кто-нибудь предложит альтернативу, где такое невозможно, но при этом не надо ждать миллион лет пока посчитается 2+2.

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

Я бы сказак, что это проблема топикстартера (вернее, автора исходного кода, ТС просто притащил это сюда). Мне кажется, что компиляторы надо завстваить по умолчанию при UB форматировать диск в несколько проходов, чтобы народ не писал код с UB. Технофашизм as it is. Так победимъ!

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

Мне кажется, кто-то ничего не понимает ни про то, как работают совеременные компиляторы, ни про UB.

++

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

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

А в других языках кто-то другой отвечает за корректность программы?

Хотя, может быть в Java тимлидер/аркитект/менеджер отвечает за корректность программы. А обычный разработчик — нет.

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

Не видал никаких поней в книге по си, только предупреждение о том, что ответственность за корректность программы лежит на программисте. Далее есть 2 варианта: принять это условие и обеспечивать корректность своими силами или не принять и не писать на С\С++. От ваших комментариев веет ненавистью к С\С++, надеюсь никто не держит пистолет у вашего виска?

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

всё ещё хуже: миску риса не дадут

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

Корректных программ на C или C++ нет, не считая хелловорлдов, и то не всех. Есть достаточно корректные программы. И проблема в том, что компиляторы, развиваясь, двигают границу между ними.

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

undefined - это значит, что оператор может сотону скастовать, сегфолтнуться, током ударить, или еще что-то сделать. По русски это должно переводиться как «за последствия не отвечаем».

По-русски это должно переводится как «последствия не знаешь ни ты, ни я, ни создатели стандарта, а только создатели компилятора».

в x86 полно undefined - например вот этот бит eflags после такой-то инструкции undefined означает, что там может хранить что угодно, а не то что данная инструкция взорвёт процессор

В том то и дело, что стандартом неопределенно, что будет по такому-то биту такого регистра после такой-то инструкции, это зависит от реализации.

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

дружественнее было бы вставить туда builtin_trap

Что нарушило бы логику работы программы, согласно которой переменная Do всегда инициализирована EraseAll, когда доходит до её вызова. Если бы было иначе, то программист бы изначально написал программу как-то так: static Function Do = [](){ std::abort(); }. С какого перепугу компилятор будет неявно лезть корректировать логику работы программы, раздувать и замедлять код? Максимум что он должен себе позволять это показать ошибку на этапе компиляции, заставив разработчика явно исправить свой код таким образом, чтобы он соответствовал одному из вариантов: либо проинициализировать Do=abort, как предлагают одни, либо Do=EraseAll как следует из описания стандарта, либо ещё что-то, что разработчик держал в уме, но забыл дописать.

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

только создатели компилятора

Nyet. И они тоже не знают. Более того, результат может меняться от запуска к запуску или от выполнения к выполнению.

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

cat add.adb

function add(a : integer; b : integer) return integer is
begin
  return a +b;
end add;

gnatgcc -s -O2 add.adb
objdump -D add.o
cat add.s

_ada_add:
.LFB2:
        .cfi_startproc
        leal    (%rdi,%rsi), %eax
        ret
        .cfi_endproc

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

Nyet. И они тоже не знают. Более того, результат может меняться от запуска к запуску или от выполнения к выполнению.

Nyet. Они могут отлавливать такие ситуации (к примеру безопасный интерпретатор С). Могут сообщить ошибку времени компиляции (если можно проверить статически). Могут забить и оставить будь что будет (разыменование NULL, но мы то знаем, что это отлавливается). Стандарт не требует от них ничего, но он и не заставляет их ничего не делать.

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

Что нарушило бы логику работы программы, согласно которой переменная Do всегда инициализирована EraseAll

Ненене, я о другом совсем

int main()
{
  if (Do)
    return EraseAll();
  else
    __builtin_trap();
}
annulen ★★★★★
()
Ответ на: комментарий от annulen

Я против того, чтобы компилятор вставлял if-ы туда, куда я его не просил. Если только это будет способствовать улучшению производительности, а не ухудшению.

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

Мы косвенный вызов заменили на прямой, да еще и в данном случае сделали инлайн. Конечно версия с ифом будет быстрее

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

Ненене, я о другом совсем

Так я о том же. Этот код имел бы смысл, если бы Do не была объявлена как static, в таком случае такое вставление __builtin_trap оправдано. Но она объявлена как static, а значит компилятор просто следуя стандарту решает соптимизировать код как ему вздумается. Вместо того, чтобы заставить это сделать разработчика.

Когда программы неявно дописывают за тебя код это не просто нарушает его логику, но и запутывает читателя. Помните первое правило программрования: программа пишется для человека, а не для машины. Если в такой программе существует переменная Do и при её вызове она может быть неинициализирована, то как позволите читателю кода понять логику работы, как задумал её автор? Правильно, никак. Вернее, вариантов трактовки что было в голове у автора может быть больше одной. А значит в коде семантический баг и он обязан быть исправлен.

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

Мы косвенный вызов заменили на прямой

В косвенном вызове читалась переменная Do, в if (Do) читается переменная Do. Но теперь ещё проверяется её значение, вместо того, чтобы просто вызвать функцию по этому адресу. В чём профит?

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

Они могут отлавливать такие ситуации

Ты сам правильно сказал, стандарт не требует ничего в данно случае. Так что утверждение «разработчики компилятора знают» не верно в общем. Могуь знать - это да. А могут и не знать.

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

В случае с кодом из ОП-поста все совсем очевидно - Do нигде не инициализирована. Компилятор может предупредить при компиляции, может сгенерировать

__its_a_trap();

а может и сделать то, что делает сейчас.

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

Вернее, конечно инициализирована - нулем. Но в остальном верно.

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

В случае с кодом из ОП-поста все совсем очевидно - Do нигде не инициализирована.

Чё?

Вернее, конечно инициализирована - нулем.

Чё?

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

Я считаю, что код в Вызов никогда не вызываемой функции (комментарий) полностью эквивалентен исходному, исключительно из-за того что Do объявлена как static и у нее всего два возможных значения - 0 и &EraseAll. Единственное изменение - замена вызова 0 на trap, так как в else UB и лучше красиво грохнуться, чем предполагать что этот путь невозможен.

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

Выше цитировали стандарт, статические глобальные переменные инициализируются нулем при старте

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

В т.ч. и вызвать EraseAll вместо нуля. Вроде легально, так как вызов нуля - UB, но не очень дружественно к пользователю

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

Уже и ссылку на оригинальный пост приводили, где всё объяснено и в этой теме объясняли (Вызов никогда не вызываемой функции (комментарий)), но кому-то до сих пор «очевидно», что в Do будет всегда ноль. Феноменальная необучаемость.

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

В чём профит?

Если без инлайна - то в том, что при прямом вызове код EraseAll начнет обрабатываться в пайплайне еще до того, как будет известно значение Do. А с инлайном вызова функции просто не будет

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

Do нигде не инициализирована.

она изначально забита нулями. ну так по стандарту. но вот дальше начинается какая-то эвристика «нормальная программа никогда null не вызовет». это им кто такое сказал? null может как раз стоять для отлова ошибок, чтоб разименовал и бдыщь всё упало а не пошло колобродить.

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

это им кто такое сказал?

Стандарт. Слышал о таком?

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

null может как раз стоять для отлова ошибок, чтоб разименовал и бдыщь всё упало

Говнокодеры продолжают защищать свой говнокод. Что в лоб что по лбу.

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

Феноменальная необучаемость.

Не знаю, почему у тебя так плохо с обучаемостью.

1. Мы говорим исключительно про код ТСа, без всяких «а вот у меня в другом файле», потому что это неконструктивно (других файлов не приложено, гадать можно бесконечно). Так вот, в коде ТСа Do всегда nullptr.

2. Даже если мы рассмотрим комментарий, на который ты ссылаешься и который говорит, что

А если у меня в другом файле определён глобальный объект, чей конструктор вызывает NeverCalled? Тогда поведение будет полностью определено.

то это тоже неверно, т.к. порядок инициализации статических переменных в разных translation unit неопределен. Поэтому Do все еще может быть nullptr, а может и не быть.

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

она изначально забита нулями

Я поправился ниже. Do == nullptr, потому что указатели инициализируются nullptr

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

А если у меня в другом файле определён глобальный объект, чей конструктор вызывает NeverCalled? Тогда поведение будет полностью определено.

то это тоже неверно

Пруфани-ка.

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

порядок инициализации статических переменных в разных translation unit неопределен

Однако все они инициализируются до main.

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

См. C++17 draft (N4606): 3.6.3 (2). Там достаточно много текста, чтобы сюда вставлять, а выдирать из контекста я не хочу. Если интересуешься - прочитаешь. Если я ошибся - пиши конккретно, где и в чем, будем разбираться.

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

Да, но из этого ничего не следует. Тот «мифический» (потому что в оригинальном коде об этом ни слова, это лоровцы уже додумывают на ходу) глобальный объект может быть инициализирован первым, вызвать NeverCalled, который инициализирует Do, а потом будет инициализирована Do и по стандарту в nullptr.

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

Тот «мифический» (потому что в оригинальном коде об этом ни слова, это лоровцы уже додумывают на ходу) глобальный объект может быть инициализирован первым, вызвать NeverCalled, который инициализирует Do, а потом будет инициализирована Do и по стандарту в nullptr.

Такое по стандарту быть не может.

А 3.6.3 в стандарте нет.

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

Такое по стандарту быть не может.

Скажи пункт, в котором это сказано или скопируй сюда текст, пожалуйста.

А 3.6.3 в стандарте нет

Указанная версию документа сейчас открыта передо мной и там все есть. Не знаю, куда ты смотришь.

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

Не знаю, куда ты смотришь.

В 4659. Оно менее тухло, чем 4606

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

А, пардон, я не дочитал до конца, думал zero в п.3. Так или иначе, это не имеет значения, потому что «мифический» объект, имеющий конструктор, будет dynamic initialized, т.к. его конструктор не может быть constexpr, т.к. NeverCalled не constexpr функция, а ему надо как-то ее вызвать. И стандарт оговорит, что static initiaalizations всегда идет перед dynamic initialization.

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

Так что выходит, сначала Do будет zero-initialized, а потом уже мифический объект может ее повторно инициализировать. Выходят я был не прав, утверждая, что порядок в данном случае не определен

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

Ну мы гадаем не видя кода. По ссылке у А дефолтный конструктор, что в коде «мифического объекта» и как он инициализируется (вдруг A = A() или A(123)) - мы не знаем.

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

Кстати, раз уж мы говорим об инициализации. Рассмотрим пример:


struct X
{
    X() : m(42) {};
    int m;
};

X x;

int main()
{
    return 0;
}

Получается, по стандарту (С++17), сначала x будет zero initialized и x.m == 0, а потом dynamically initialized и x.m == 42? Причем первое может быть как compile-time, так и нет (второе обычно всегда run-time)?

maverik ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.