Я не уверен, но я думаю, что ответ - нет, по довольно тонким причинам. Я спросил о теоретической информатике несколько лет назад и не получил ответа, который выходит за рамки того, что я здесь представлю.
В большинстве языков программирования вы можете моделировать машину Тьюринга:
- моделирование конечного автомата с помощью программы, использующей конечный объем памяти;
- моделирование ленты с помощью пары связанных списков целых чисел, представляющих содержимое ленты до и после текущей позиции. Перемещение указателя означает перенос заголовка одного из списков на другой список.
Конкретная реализация, работающая на компьютере, могла бы исчерпать память, если бы лента стала слишком длинной, но идеальная реализация могла бы точно выполнять машинную программу Тьюринга. Это можно сделать ручкой и бумагой или покупкой компьютера с большим объемом памяти и компилятором, ориентированным на архитектуру с большим количеством битов на слово и т. Д., Если программе когда-либо не хватает памяти.
Это не работает в C, потому что невозможно иметь связанный список, который может расти вечно: всегда есть какое-то ограничение на количество узлов.
Чтобы объяснить почему, мне сначала нужно объяснить, что такое реализация на Си. C на самом деле семейство языков программирования. Стандарт ISO C (точнее, конкретная версия этого стандарта) определяет (с уровнем формальности, который допускает английский язык) синтаксис и семантику семейства языков программирования. C имеет много неопределенного поведения и поведения, определенного для реализации, «Реализация» C кодифицирует все поведение, определяемое реализацией (список вещей для кодификации приведен в приложении J для C99). Каждая реализация C - это отдельный язык программирования. Обратите внимание, что значение слова «реализация» немного своеобразно: оно действительно означает языковой вариант, может быть несколько разных программ компилятора, которые реализуют один и тот же языковой вариант.
В данной реализации C байт имеет возможных значения CHAR_BIT . Все данные могут быть представлены в виде массива байтов: тип имеет максимум
2 CHAR_BIT × sizeof (t) возможных значений. Это число варьируется в разных реализациях C, но для данной реализации C оно является константой.2CHAR_BITt
2CHAR_BIT×sizeof(t)
В частности, указатели могут принимать только . Это означает, что существует конечное максимальное количество адресуемых объектов.2CHAR_BIT×sizeof(void*)
Значения CHAR_BIT
и sizeof(void*)
являются наблюдаемыми, поэтому, если у вас не хватает памяти, вы не можете просто возобновить работу своей программы с большими значениями для этих параметров. Вы будете запускать программу под другим языком программирования - другой реализацией Си.
Если программы на языке могут иметь только ограниченное число состояний, то язык программирования не более выразителен, чем конечные автоматы. Фрагмент C, который ограничен адресным хранилищем, допускает самое большее состояний программы, где n - размер абстрактного синтаксического дерева программы (представляющего состояние потока управления), поэтому это Программа может быть смоделирована конечным автоматом с таким количеством состояний. Если C более выразителен, это должно быть сделано с помощью других функций.n × 2CHAR_BIT × sizeof (void *)N
C напрямую не навязывает максимальную глубину рекурсии. Реализация может иметь максимум, но также может не иметь. Но как мы общаемся между вызовом функции и ее родителем? Аргументы бесполезны, если они адресуемы, потому что это косвенно ограничило бы глубину рекурсии: если у вас есть функция, int f(int x) { … f(…) …}
то все вхождения в x
активных кадрах f
имеют свой собственный адрес, и поэтому число вложенных вызовов ограничено числом возможных адресов для x
.
Программа AC может использовать неадресуемые хранилища в виде register
переменных. «Нормальные» реализации могут иметь только небольшое конечное количество переменных, которые не имеют адреса, но теоретически реализация может позволить неограниченный объем register
памяти. В такой реализации вы можете делать неограниченное количество рекурсивных вызовов функции, пока ее аргумент есть register
. Но поскольку аргументы таковы register
, вы не можете сделать на них указатель, и поэтому вам необходимо явно копировать их данные: вы можете передавать только конечный объем данных, а не структуру данных произвольного размера, состоящую из указателей.
С неограниченной глубиной рекурсии и ограничением, что функция может получать данные только от своего прямого вызывающего ( register
аргументы) и возвращать данные своему прямому вызывающему (возвращаемое значение функции), вы получаете мощность детерминированных автоматов выталкивания .
Я не могу найти способ пойти дальше.
(Конечно, вы можете заставить программу хранить содержимое ленты внешне, через функции ввода / вывода файла. Но тогда вы не будете спрашивать, является ли C завершенным по Тьюрингу, но является ли C плюс бесконечная система хранения полным по Тьюрингу, чтобы ответ - скучное «да». Вы также можете определить хранилище как вызов оракула Тьюринга fopen("oracle", "r+")
, fwrite
начальное содержимое ленты и fread
обратно конечное содержимое ленты.)