• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Inkrementacja zmiennej przez rekurencję

Aruba Cloud VPS - 50% taniej przez 3 miesiące!
+2 głosów
530 wizyt
pytanie zadane 10 marca 2022 w C i C++ przez Bilib Użytkownik (990 p.)

Rozpatrzmy poniższy, prosty program w języku C/C++:

#include <stdio.h>
int increment(int n)
{
   return (n<100000) ? increment(n+1) : n;
}
int main()
{
   int a=increment(1);
   printf("%d", a);
}

Program powinien dokonać wielokrotnego wywołania funkcji increment przez rekurencję, która wykonuje się tak długo, aż wartość zmiennej n nie będzie równa 100000 (i taka liczba ma być wypisana). Niestety, tuż po uruchomieniu programu zwracany jest jedynie błąd  -1073741571 (0xC00000FD). Tego problemu nie ma, gdy w warunku zamiast wartości 100000 jest liczba mniejsza od 65144, np. return (n<50000) ? increment(n+1) : n; (program wypisuje poprawnie 50000). Gdy zmienimy argument funkcji increment na 2, najwyższą porównywaną liczbą będzie 65144, gdy będzie to 3 - 65145 itd.

W czym tkwi problem?

2 odpowiedzi

+2 głosów
odpowiedź 10 marca 2022 przez rafal.budzis Szeryf (85,380 p.)

Odpowiedz to stos wywołań ;) Gdy wykonujesz rekurencje to komputer musi sobie zapamiętać że

- wartość pierwszego wywołania funkcji to
- wartość drugiego wywołania funkcji której
- wartość to trzecie wywołanie funkcji której
- wartość to 4.
- więc trzecie wywołanie zwraca 4 
- a skoro trzecie to 4 to drugie to też 4
- a skoro drugie zwraca 4 to 
- pierwsze też może zwrócić 4

Chodzi o to że podczas rekurencji musisz przechowywać w pamięci całą kolejność wywołań funkcji aby potem zacząć przekazywać do niej wartość od tyłu. Niestety przechowywanie kolejności wywołań funkcji ma swoje ograniczenia i przekroczenie ograniczenia jest na tyle popularne że przyczyniło się do nazwania jednego forum internetowego: stack overflow ;)

+1 głos
odpowiedź 11 marca 2022 przez mokrowski Mędrzec (156,480 p.)
Popatrz na linijkę 15.

https://godbolt.org/z/aP7jW7r5T

Jak widzisz, jest tam obecna instrukcja asemblerowa call. Oznacza to że przy każdym wywołaniu, odkładane są informacje na stosie. Przy tak dużej ilości wywołań, następuje przepełnienie stosu.

A teraz dodaj przełącznik optymalizacji np ze względu na prędkość.

https://godbolt.org/z/PYcnscnzx

Jak widać kompilator zamienił call, na jmp.

A dzieje się tak dlatego, że w standardzie języka C ani C++ nigdzie nie ma zapisu że kompilator ma obsługiwać rekurencję. Tylko od życzliwości dostawcy kompilatora zależy, czy dany kompilator będzie to obsługiwał.

BTW: g++ obsługuje rekurencję poprawnie już bez przełączników optymalizacji.
komentarz 11 marca 2022 przez Oscar Nałogowiec (29,340 p.)
Dziwnie piszesz. Nie studiowałem dogłębnie standardów, ale zawsze mnie uczono, że C obsługuje rekurencje.

To, że kompilator w ramach optymalizacji sobie coś tam pozmienia to inna sprawa.
komentarz 11 marca 2022 przez mokrowski Mędrzec (156,480 p.)

Ok, wykonałem nieprecyzyjny skrót myślowy. Sugerowałem się pytaniem w trybie "a dlaczego to nie działa". No to może nieco obszerniej.

C tak jak i C++, nigdzie nie deklaruje w standardzie że rekurencja ma być obsługiwana "z optymalizacją" i na jakim poziomie samej optymalizacji kompilatora. Taka rekurencja nazywana jest tail call lub tail recursion ( https://en.wikipedia.org/wiki/Tail_call ). Haskell to ma, Scheme i pochodne Lisp'a C i C++ ... (jak większość języków imperatywnych) ... może tak a może nie... :/

Natomiast standard C++, opisuje np. obsługę main() :) Nie powinno się jej wołać rekurencyjnie (6.9.3.1 2.2.3) http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2020/n4849.pdf

Dlatego rekurencja dla C i C++ dla kodu produkcyjnego jest zabroniona, bo nie każdy dostawca i nie na każdym poziomie optymalizacji swojego kompilatora, może ją implementować.

https://helloacm.com/understanding-tail-recursion-visual-studio-c-assembly-view/

... tu trochę o trampolinie która zapewni obsługę optymalizacji zawsze...

https://www.artificialworlds.net/blog/2012/04/30/tail-call-optimisation-in-cpp/

Uff.. Mam nadzieję że teraz jasne :)

 

Podobne pytania

0 głosów
4 odpowiedzi 1,213 wizyt
+1 głos
0 odpowiedzi 572 wizyt
pytanie zadane 12 marca 2022 w C i C++ przez Bilib Użytkownik (990 p.)
0 głosów
2 odpowiedzi 1,802 wizyt
pytanie zadane 7 listopada 2022 w Python przez niezalogowany

93,187 zapytań

142,203 odpowiedzi

322,023 komentarzy

62,515 pasjonatów

Advent of Code 2024

Top 15 użytkowników

  1. 2581p. - dia-Chann
  2. 2537p. - Łukasz Piwowar
  3. 2528p. - Łukasz Eckert
  4. 2514p. - CC PL
  5. 2476p. - Tomasz Bielak
  6. 2445p. - Łukasz Siedlecki
  7. 2443p. - rucin93
  8. 2201p. - Michal Drewniak
  9. 2156p. - Marcin Putra
  10. 2152p. - Adrian Wieprzkowicz
  11. 2105p. - Mikbac
  12. 1941p. - Anonim 3619784
  13. 1733p. - rafalszastok
  14. 1701p. - Michał Telesz
  15. 1580p. - ssynowiec
Szczegóły i pełne wyniki

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj

Wprowadzenie do ITsec, tom 1 Wprowadzenie do ITsec, tom 2

Można już zamawiać dwa tomy książek o ITsec pt. "Wprowadzenie do bezpieczeństwa IT" - mamy dla Was kod: pasja (użyjcie go w koszyku), dzięki któremu uzyskamy aż 15% zniżki! Dziękujemy ekipie Sekuraka za fajny rabat dla naszej Społeczności!

...