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();
}
★★★☆☆

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

Да, имелось ввиду

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

static X x;

int main()
{
    return 0;
}

Хотя тут всё-равно, т.к. static в данном случае указывает лишь на linkage. Оба x (из обоих примеров) должны быть static storage duration

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

Чтобы запуститься, граалю нужна классическая жаба.

Понял.

почему GPL2?

скорей всего потому, что классическая жаба написана на gplv2 и релицензировать ее уже нельзя, по той же причине, что и ведро линукса

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

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

по-моему вам уже стоит перестать терять время, и перейти от рассуждений к прямым оскорблениям и нанесению тяжких телесных авторам стандарта

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

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

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

маскирует некорректный код под корректный

в оп-примере нет никакого «некорректного» (ill-formed) кода

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

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

До сих пор от истории с мьютексами не отошёл? :-)

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

Имхо, это уже какая-то собственная вольная трактовка стандарта.

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf

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

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

как-то нерелевантненько

наброс в оп-посте был куда удачнее :)

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

спасибо

в том посте многие обделали штанишки

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

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

ты же не думаешь что на это специально забивают?

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

Отследить UB и исключить при этом false positive часто просто нереально.

gcc при компиляции того же кода с той же опцией -Os сегфолтит программу, а не запускает дефолтную функцию. А значит, в данном случае отследить всё-таки реально.

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

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

Ты или кто-то другой (как-никак со времени публикации блогпоста прошло немало времени) уже создали тикет в багтрекере? Ссылкой поделишься?

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

придётся процитировать себя (хотя это и некрасиво): ты не знаешь си. и отсутствие определённых оптимизаций в гцц тебя никоим образом не оправдывает

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

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

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

Он это и делает, по возможности.

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

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

Ну вот. А просили посчитать 2+2 без ожидания миллион лет. Если каждый раз, при вычислении 2+2 проверять, нет ли переполнения, то всё будет тормозить.

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

выучишь язык — приходи беседовать об оптимизации, лалка

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

undefined это означает «за последствия не отвечаем». например будет сегфолт, исключение #PF, обычное обращение к оперативке. undefined позволяет ряд оптимизаций, но оптимизации не должны нарушать логику программы. но undefined почему-то положили оправданием для его расползания на соседние операции. то есть если мы обратились на строке 7500 к такой переменной, то компилятор имеет право колбасить везде где хочет.

Полностью согласен. И совершенно не понимаю, как такую очевидную вещь не могут понять многие другие, отметившиеся в этом треде.

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

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

Так не дело компилятора додумывать за программиста и исправлять его ошибки. Компилятор должен только откомпилировать исходный код в исполняемый, не нарушив логику. А если в программе ошибка, то указать на неё в соответствующем сообщении, а не исправлять по своему усмотрению.

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

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

Т. е. компилятор оптимизирует UB-программу и не оптимизирует остальные? А смысл?

в любом случае, код соответствует стандарту.

Ok, возьмём пример с рулём автомобиля. Уверен, что характеристики рулевого управления как-то стандартизированы. Когда водитель поворачивает руль вправо или влево, автомобиль поворачивает в ту же сторону. Если водитель держит руль прямо, то и автомобиль едет прямо (при нормальном сцеплении с дорогой). А вот отпускать руль во время движения водителю категорически не рекомендуется. Если же он это сделает, наступает то самое неопределённое поведение: переднеприводный автомобиль на ровной дороге скорее всего продолжит движение прямо, а вот заднеприводный может начать произвольно поворачивать в разные стороны. Следует ли из этого, что производитель автомобиля может намеренно заложить в него функцию, которая при отпускании руля во время движения начнёт поворачивать его влево, на встречку? Ведь это соответствует стандарту (неопределённое направление).

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

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

gcc. Только gcc тоже компилирует си/си++. Из чего следует, что это проблема компилятора, а не языка. ТС ошибся.

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

А значит это баг компилятора, а не фича стандарта.

можно натянуть сову на глобус

Вот именно. Но не нужно, очевидно же. :-)

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

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

Вот и я спрашиваю, с какого перепугу он это делает?

раздувать и замедлять код?

Зачем? gcc просто сегфолтит, и всё становится ясно. clang же меняет логику так, что нерабочая программа начинает работать, но так ли, как задумывалось? Имхо, это намного опаснее честного сегфолта.

показать ошибку на этапе компиляции

И ошибку или хотя бы предупреждение показать тоже не мешает, согласен.

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

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

Именно то, что делает в нашем случае clang.

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

Значит программист скорее всего ошибся. Но вместо того, чтоб предупредить его об этом, clang прячет ошибку, самостоятельно решая, что программа должна делать, а чего не должна.

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

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

Кстати, решил попробовать добавить в описание NeverCalled static, чтоб его нельзя было вызвать из другого модуля. И вот что у меня получилось. utf8nowhere, maverik, annulen, ckotinko, Dendy, numas13 и tim239, возможно, вам будет интересно, ну а если нет, то звиняйте. Оказывается, разрабы clang не ошиблись, а те ещё троли, нещадно тролят нашего брата-быдлокодера этим вашим стандартом!

#include <cstdlib>

typedef int (*Function)();

static Function Do;

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

static void NeverCalled() {
  Do = EraseAll;  
}

int main() {
  return Do();
}
$ clang -o test_fnull -Os test_fnull.cpp
$ ./test_fnull
Недопустимая инструкция
$ clang -S -Os test_fnull.cpp

Теперь смотрим сгенерированный ассемблерный код в test_fnull.s:

        .text
        .file   "test_fnull.cpp"
        .globl  main
        .type   main,@function
main:                                   # @main
        .cfi_startproc
# BB#0:
        ud2
.Ltmp0:
        .size   main, .Ltmp0-main
        .cfi_endproc


        .ident  "Debian clang version 3.5.0-10 (tags/RELEASE_350/final) (based on LLVM 3.5.0)"
        .section        ".note.GNU-stack","",@progbits

Недопустимой инструкцией здесь является ud2. Вот зачем так делать? Они увидели, что код некорректный и никакие другие модули не могут вызвать NeverCalled или как-то иначе изменить Do. Вот почему бы не выдать предупреждение? Но его нет. Почему бы не вызвать 0 из объектника, как и написано в программе, вызвав тем самым сегфолт? Тогда погромист запустит код в дебагере, наткнётся на этот самый 0 и всё поймёт, если он не до конца пропил мозги. Но компилятор намеренно вставляет в код недопустимую инструкцию, чтоб программист и в дебагере ничего не увидел кроме того, что компилятор сгенерил какую-то хрень. Причём хрень эта никак не связана с ошибкой программиста, т. к. недопустимые инструкции компилятор генерить не должен. Он или компилит нормально, или вообще не компилит, выводя ошибки. Это тоже такая фича? По аналогии с автомобилем это явный намеренный выезд влево на встречку, зашитый в автомобиль, при отпускании водителем руля. Нормальная такая фича. Главное, шо по стандарту!

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

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

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

См. C++17 draft (N4606): 3.6.3 (2).

Насколько я понял, там про порядок инициализации разных статических объектов относительно друг друга, а не про то, что глобальные объекты могут в каких-то случаях инициализироваться после вызова main().

глобальный объект может быть инициализирован первым, вызвать NeverCalled, который инициализирует Do, а потом будет инициализирована Do и по стандарту в nullptr.

Нет. Если Do явно не был присвоен 0, то он инициализируется по умолчанию 0 только в том случае, если ему больше нигде ничего явно не присваивается. Т. е. до всех остальных инициализаций. Ссылку на п. в стандарте не дам, т. к. для меня это очевидно, но если у кого-то есть информация, что я не прав, буду рад контр-ссылкам.

aureliano15 ()

маскирует некорректный код под корректный

в оп-примере нет никакого «некорректного» (ill-formed) кода

А некорректный — это не только содержащий синтаксические ошибки, но и логические.

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

А я его об этом просил? Может я хочу свести счёты с жизнью, а инвалидом становиться не хочу.

Не устаю приводить пример с умным рулём автомобиля.

aureliano15 ()
Ответ на: для того чтобы ты понял... от anonymous

для того чтобы ты понял...
сравни следующий код:
https://godbolt.org/g/pTesqv
https://godbolt.org/g/7AoAL2

И что я должен понять? Что если Do не статическая, то clang не делает идиотских предположений? Ну хоть на этом спасибо. А то бы это был вообще эпик-фэйл.

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

Да им, похоже, вообще на всё плевать. Они могут и недопустимые команды в код вставить, с них станется. Слава богу, не всем. Разработчикам gcc не плевать.

(говорю как имевший отношение)

В прошедшем времени? Ну хоть это слава богу.

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

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

Ты или кто-то другой (как-никак со времени публикации блогпоста прошло немало времени) уже создали тикет в багтрекере?

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

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

придётся процитировать себя (хотя это и некрасиво): ты не знаешь си.

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

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

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

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

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

Возьми vhdl, спроектируй проц, с поддержкай мму (для горячо любимого линукса, хехе), с флажком переполнение, или флажком активации аппаратного исключения от переполнением. Вложи тонну нефти в реклами, производство айсиков, убеди людей, что переполнение - враг народа большая проблема. Заставь интел реализовать твою архитектуру вместо amd64, и убедись, что сие нахрен никому не сдалось. Хочешь защиту от переполнения - gnato. Но получишь лишний такт (тяжесть - єто надежность)) Хочешь перформанс - юзай что из sse, dsp, opencl, vhdl и не ной. Неспособен освоить - иди в сварщики

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

ты не можешь гарантированно (defined) упасть при разыменовании нулевого указателя ни в си, ни в крестах. если тебе нужны гарантии — бери жабу. что и имел в виду оп.

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

Не поможет. Например - переполнение int при побитовом сдвиге влево - UB. А это можно определить только в рантайме с накладными расходами.

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

Почему бы не вызвать 0 из объектника, как и написано в программе, вызвав тем самым сегфолт?

Ты хотел чтобы прога упала — они упала. Какая разница, от SIGSEGV или ud2.

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

ты не можешь гарантированно (defined) упасть при разыменовании нулевого указателя ни в си, ни в крестах. если тебе нужны гарантии — бери жабу. что и имел в виду оп.

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

Вот почему printf при указании 0 в качестве адреса строки для вывода, выводит слово null, а не лезет туда и не заменяет вывод строки по 0 адресу недопустимой командой? Потому что стандартную библиотеку писали адекватные люди, а не троли или фрики.

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

переполнение int при побитовом сдвиге влево - UB. А это можно определить только в рантайме с накладными расходами.

Я уже писал об этом в другом треде. Попробуй переполнить при сдвиге влево int в любом си-компиляторе с любыми опциями оптимизации и увидишь, что ничего непредсказуемого не произойдёт, кроме того, что получившееся число действительно нельзя будет интерпретировать как умножение исходного число на степень 2. Но это справедливо для любого переполнения, если не использовать ассемблер x86, где при арифметическом переполнении устанавливается соответствующий флаг, а переполнение переносится в другой регистр. Но к си без встроенного ассемблера это не относится.

Единственно добавлю к тому каменту, что если сдвиг будет больше самого размера сдвигаемого числа (т. е. больше 31 для int_32t, 63 для int_64t и т. д.), то компилятор может обрезать старшие биты 2-ого операнда (на что сдвигаем), в результате сдвиг 64-битного числа на 64 бита будет идентичен сдвигу на 1. Но это справедливо и для правого сдвига. И в большинстве случаев (когда то, на что сдвигаем, является константой) известно на этапе компиляции. В других же случаях самому применить маску тоже несложно и не дорого.

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

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

Почему бы не вызвать 0 из объектника, как и написано в программе, вызвав тем самым сегфолт?

Ты хотел чтобы прога упала — они упала. Какая разница, от SIGSEGV или ud2.

Я хотел другого:

Тогда погромист запустит код в дебагере, наткнётся на этот самый 0 и всё поймёт, если он не до конца пропил мозги.

А этого:

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

я не хотел.

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

Это всё хорошо. Только стандарту Си пофиг на процы и компиляторы.

Любители «практики» и прочего хардвара потом будут плакаться как адоби флэш про memmove.

Язык Си работает на modulo2 арифметике (согласно стандарту). Соответственно, на троичное железо ему тоже глубоко пофиг.

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

Любители «практики» и прочего хардвара потом будут плакаться как адоби флэш про memmove.

Это несколько другая история. Всегда была memmove, допускавшая безопасное перемещение внутри пересекающихся областей, и все об этом знали (по крайней мере никто этого не скрывал). И даже с учётом этого были голоса весьма уважаемых людей считать новое поведение memcpy багом.

В случае же со сдвигами реальной эффективной альтернативы нет, зато кода, использующего сдвиги с переполнением или сдвигающего отрицательные числа (чаще всего -1) — до хрена.

Так что, думаю, если найдутся разрабы какого-то компилятора, которые рискнут изменить такое поведение левого сдвига, то плакаться в конечном итоге будут они сами, потому что все просто перестанут пользоваться таким компилятором либо создадут форк (в случае со свободным ПО).

Язык Си работает на modulo2 арифметике (согласно стандарту). Соответственно, на троичное железо ему тоже глубоко пофиг.

Т. е. ты хочешь сказать, что если вдруг по каким-то неведомым причинам снова появятся и распространятся троичные машины, никто не попытается портировать на них си, и этот язык умрёт? Но ведь если такое произойдёт, то это будет ещё хуже, чем просто поправить сдвиги или заменить memcpy на memmove. Тебе полностью придётся переписывать всю программу от корки до корки, предварительно изучив новый язык, ориентированный на троичную арифметику.

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

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

Что будет в стандарте Си-2050 - увидим. А пока что C11 говорит об UB при переполнении. Любители завязываться на «здравый смысл» свободны делать это.

А с мьютексами ничего. std::mutex не является thread-safe и пользоваться им безопасно можно в очень ограниченном множестве случаев. Сишными потоками (как и Си) не пользуюсь напрямую.

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