Nie jestem pewien, ale myślę, że odpowiedź brzmi „nie” z dość subtelnych powodów. I poprosił o Theoretical Computer Science kilka lat temu i nie uzyskać odpowiedź, która wykracza poza to, co ja tu obecnych.
W większości języków programowania można symulować maszynę Turinga poprzez:
- symulowanie skończonego automatu za pomocą programu, który wykorzystuje skończoną ilość pamięci;
- symulowanie taśmy za pomocą pary połączonych list liczb całkowitych reprezentujących zawartość taśmy przed bieżącą pozycją i po niej. Przesunięcie wskaźnika oznacza przeniesienie nagłówka jednej z list na drugą.
W konkretnej implementacji działającej na komputerze zabrakłoby pamięci, gdyby taśma była zbyt długa, ale idealna implementacja mogłaby wiernie wykonać program maszyny Turinga. Można to zrobić za pomocą długopisu i papieru lub kupując komputer z większą pamięcią i kompilatorem ukierunkowanym na architekturę z większą liczbą bitów na słowo itd., Jeśli programowi zabraknie pamięci.
To nie działa w C, ponieważ nie można mieć połączonej listy, która mogłaby rosnąć wiecznie: zawsze istnieje pewne ograniczenie liczby węzłów.
Aby wyjaśnić, dlaczego, najpierw muszę wyjaśnić, czym jest implementacja C. C to właściwie rodzina języków programowania. Standard ISO C (dokładniej, specyficzna wersja tego standardu) określa (z poziomu formalizmu, który pozwala angielska) składni i semantyki to rodzina języków programowania. C ma wiele niezdefiniowanych zachowań i zachowań zdefiniowanych w implementacji. „Implementacja” C kodyfikuje wszystkie zachowania zdefiniowane w implementacji (lista rzeczy do skodyfikowania znajduje się w dodatku J dla C99). Każda implementacja C jest oddzielnym językiem programowania. Zauważ, że znaczenie słowa „implementacja” jest nieco dziwne: tak naprawdę oznacza wariant językowy, może istnieć wiele różnych programów kompilujących, które implementują ten sam wariant językowy.
2CHAR_BITt
2CHAR_BIT×sizeof(t)
2CHAR_BIT×sizeof(void*)
Wartości CHAR_BIT
i sizeof(void*)
są obserwowalne, więc jeśli zabraknie pamięci, nie możesz po prostu wznowić działania programu z większymi wartościami dla tych parametrów. Uruchomiłbyś program w innym języku programowania - innej implementacji C.
n×2CHAR_BIT×sizeof(void*)n
C nie nakłada bezpośrednio maksymalnej głębokości rekurencji. Implementacja może mieć maksimum, ale także może nie mieć takiego. Ale w jaki sposób komunikujemy się między wywołaniem funkcji a jego rodzicem? Argumenty nie są dobre, jeśli można je adresować, ponieważ pośrednio ograniczyłoby to głębokość rekurencji: jeśli masz funkcję, int f(int x) { … f(…) …}
wszystkie wystąpienia x
aktywnych ramek f
mają swój własny adres, a więc liczba zagnieżdżonych wywołań jest ograniczona liczbą możliwych adresów dla x
.
Program AC może używać pamięci nieadresowalnej w postaci register
zmiennych. „Normalne” implementacje mogą mieć tylko niewielką, skończoną liczbę zmiennych, które nie mają adresu, ale teoretycznie implementacja może pozwolić na nieograniczoną ilość register
pamięci. W takiej implementacji można wykonać nieograniczoną liczbę rekurencyjnych wywołań funkcji, o ile są to jej argumenty register
. Ale ponieważ argumentami są register
, nie możesz zrobić dla nich wskaźnika, więc musisz jawnie skopiować ich dane: możesz przekazać tylko skończoną ilość danych, a nie strukturę danych o dowolnej wielkości złożoną ze wskaźników.
Dzięki nieograniczonej głębokości rekurencji i ograniczeniu, że funkcja może tylko pobierać dane z bezpośredniego wywołującego ( register
argumenty) i zwracać dane do bezpośredniego wywołującego (wartość zwracana przez funkcję), uzyskujesz moc deterministycznych automatów do przekazywania .
Nie mogę znaleźć sposobu, aby pójść dalej.
(Oczywiście można zmusić program do przechowywania zawartości taśmy na zewnątrz za pomocą funkcji wejścia / wyjścia pliku. Ale wtedy nie zapytałbyś, czy C jest kompletny w Turingu, ale czy C plus nieskończony system pamięci jest kompletny w Turingu, aby na które odpowiedź brzmi nudno „tak”. Równie dobrze możesz zdefiniować pamięć jako wezwanie Turinga fopen("oracle", "r+")
, fwrite
początkową zawartość taśmy i fread
cofnąć końcową zawartość taśmy).