Co to znaczy „zatruwać funkcję” w C ++?

96

Na samym końcu wystąpienia Scotta Schurra „Wprowadzenie constexpr” na CppCon pyta „Czy istnieje sposób na zatrucie funkcji”? Następnie wyjaśnia, że ​​można to zrobić (choć w niestandardowy sposób) poprzez:

  1. Umieszczenie throww constexprfunkcji
  2. Deklarowanie nierozwiązanego problemu extern const char*
  3. Odwoływanie się do nierozwiązanych externwthrow

Czuję, że trochę mi tu brakuje głębi, ale jestem ciekawy:

  • Co to znaczy „zatruwać funkcję”?
  • Jakie jest znaczenie / przydatność opisanej przez niego techniki?
sudo make install
źródło
1
Nigdy nie słyszałem o tym terminie, wyjaśnij zwięzłym przykładem!
πάντα ῥεῖ
6
@ πάνταῥεῖ, właśnie wyjaśniłem. To określenie „szeroko znane w małych kręgach”
Siergiej A
4
Mówi o zapewnieniu, że każde wywołanie constexprfunkcji jest oceniane w czasie kompilacji.
TC
@TC Right - wspomniał, że constexprfunkcja może być używana w czasie kompilacji lub w czasie wykonywania. Czy to jest sposób na wymuszenie tego, aby nie można było go używać w czasie wykonywania? Kiedy jest to przydatne?
sudo make install
3
Zwłaszcza w C ++ 11 constexprfunkcja często nie jest najbardziej wydajną implementacją z powodu ograniczeń, więc można nie chcieć, aby była oceniana w czasie wykonywania; a może jest to przypadek błędu (jak w jego przykładzie).
TC

Odpowiedzi:

106

Ogólnie odnosi się to do uczynienia funkcji bezużyteczną, np. Jeśli chcesz zakazać używania alokacji dynamicznej w programie, możesz ją „zatruć”, mallocaby nie można jej było używać.

W filmie używa go w bardziej konkretny sposób, co jest jasne, jeśli przeczytasz slajd, który jest wyświetlany, gdy mówi o zatruciu funkcji, który mówi „Sposób na wymuszenie tylko czasu kompilacji?”

Mówi więc o „zatruciu” funkcji, aby uniemożliwić jej wywołanie w czasie wykonywania, więc można ją wywołać tylko w stałych wyrażeniach. Technika polega na tym, aby mieć gałąź w funkcji, która nigdy nie jest pobierana, gdy jest wywoływana w kontekście czasu kompilacji, i sprawić, by ta gałąź zawierała coś, co spowoduje błąd.

throwEkspresja jest dozwolone w funkcji constexpr, dopóki nie zostanie osiągnięta podczas inwokacji wbudowanego wskaźnika funkcji (ponieważ nie można rzucać wyjątek w czasie kompilacji, to z natury dynamiczne działanie, jak przydzielania pamięci). Tak więc wyrażenie rzutowania, które odwołuje się do niezdefiniowanego symbolu, nie będzie używane podczas wywołań w czasie kompilacji (ponieważ kompilacja nie powiodłaby się) i nie może być używana w czasie wykonywania, ponieważ niezdefiniowany symbol powoduje błąd konsolidatora.

Ponieważ niezdefiniowany symbol nie jest „używany przez ODR” w wywołaniach funkcji w czasie kompilacji, w praktyce kompilator nie utworzy odniesienia do symbolu, więc jest OK, że jest on niezdefiniowany.

Czy to jest przydatne? Pokazuje, jak to zrobić, niekoniecznie mówiąc, że to dobry pomysł lub bardzo przydatny. Jeśli z jakiegoś powodu musisz to zrobić, jego technika może rozwiązać twój problem. Jeśli nie potrzebujesz tego, nie musisz się tym martwić.

Jednym z powodów, dla których może to być przydatne, jest sytuacja, gdy wersja operacji w czasie kompilacji nie jest tak wydajna, jak mogłaby być. Istnieją ograniczenia dotyczące rodzaju wyrażeń dozwolonych w funkcji constexpr (szczególnie w C ++ 11, niektóre ograniczenia zostały usunięte w C ++ 14). Więc możesz mieć dwie wersje funkcji do wykonywania obliczeń, jedną, która jest optymalna, ale używa wyrażeń, które nie są dozwolone w funkcji constexpr, i jedną, która jest prawidłową funkcją constexpr, ale działałaby słabo, gdyby została wywołana w run- czas. Możesz zatruć nieoptymalną wersję, aby upewnić się, że nigdy nie jest używana do wywołań w czasie wykonywania, zapewniając, że bardziej wydajna (nie będąca constexpr) wersja jest używana do wywołań w czasie wykonywania.

Uwaga: Wydajność funkcji constexpr używanej w czasie kompilacji nie jest naprawdę ważna, ponieważ i tak nie ma narzutu czasu wykonywania. Może spowolnić twoją kompilację, zmuszając kompilator do dodatkowej pracy, ale nie będzie to miało żadnego kosztu wydajności w czasie wykonywania.

Jonathan Wakely
źródło
1
Przeczytałem tekst slajdu, ale nie widziałem związku z terminem, którego używał. Teraz jest oczywiste, że to wyjaśniłeś, ale wtedy tego nie widziałem. Bardzo dziękuję za tę doskonałą odpowiedź - po prostu uwielbiam tę stronę.
sudo make install
@PravasiMeet, zadaj własne pytanie, nie przejmuj komentarzy do czyichś pytań dotyczących czegoś innego. Prostym rozwiązaniem byłoby zdefiniowanie go jako usuniętego w każdej jednostce tłumaczeniowej lub zastąpienie go własną definicją, która odwołuje się do niezdefiniowanego symbolu.
Jonathan Wakely
17

„Zatrucie” identyfikator oznacza, że ​​każde odniesienie do identyfikatora po „zatruciu” jest twardym błędem kompilatora. Technika ta może być użyta na przykład do trwałego wycofania (funkcja JEST przestarzała, nigdy jej nie używaj!).

W GCC tradycyjnie było Pragma do tego: #pragma GCC poison.

Siergiej A.
źródło
1
Tak, ale niezupełnie w znaczeniu użytym w tej przemowie.
TC
@TC, ok, prawdopodobnie powinienem to obejrzeć przed odpowiedzią :)
SergeyA