Если мы пойдем по книге (или любой другой версии спецификации языка, если вы предпочитаете), сколько вычислительной мощности может иметь реализация C?
Обратите внимание, что «реализация C» имеет техническое значение: это конкретный экземпляр спецификации языка программирования C, в котором задокументировано поведение, определяемое реализацией. Реализация AC не должна быть в состоянии работать на реальном компьютере. Он должен реализовывать весь язык, включая каждый объект, имеющий представление битовой строки, и типы, имеющие размер, определенный реализацией.
Для целей этого вопроса нет внешнего хранилища. Единственный ввод / вывод, который вы можете выполнить, - getchar
(для чтения ввода программы) и putchar
(для записи вывода программы). Также любая программа, которая вызывает неопределенное поведение, является недопустимой: допустимая программа должна иметь свое поведение, определенное спецификацией C, плюс описание реализации определенных поведением реализаций, приведенное в приложении J (для C99). Обратите внимание, что вызов библиотечных функций, которые не упомянуты в стандарте, является неопределенным поведением.
Моя первоначальная реакция заключалась в том, что реализация C - это не что иное, как конечный автомат, потому что он имеет ограничение на объем адресуемой памяти (вы не можете адресовать больше, чем sizeof(char*) * CHAR_BIT
биты памяти, так как разные адреса памяти должны иметь разные битовые комбинации при хранении в байтовом указателе).
Однако я думаю, что реализация может сделать больше, чем это. Насколько я могу судить, стандарт не ограничивает глубину рекурсии. Таким образом, вы можете сделать столько рекурсивных вызовов функций, сколько захотите, только все, кроме конечного числа вызовов, должны использовать register
аргументы non-addressable ( ). Таким образом, реализация C, которая допускает произвольную рекурсию и не имеет ограничений на количество register
объектов, может кодировать детерминированные автоматы нажатия.
Это верно? Можете ли вы найти более мощную реализацию C? Существует ли полная реализация Тьюринга C?
источник
Ответы:
Как отмечено в вопросе, стандарт C требует, чтобы существовало значение UCHAR_MAX, чтобы каждая переменная типаUСЧАСA R _ MA Xев я ге о е( У п ев я гн д д c h a r ∗ ) 101010
unsigned char
всегда содержала значение от 0 до UCHAR_MAX включительно. Кроме того, требуется, чтобы каждый динамически размещенный объект был представлен последовательностью байтов, которая может быть идентифицирована с помощью указателя типаunsigned char*
, и чтобы существовала константаsizeof(unsigned char*)
, так чтобы каждый указатель этого типа был идентифицируем с помощью последовательностиsizeof(unsigned char *)
значений типаunsigned char
. Таким образом, число объектов, которые могут быть динамически распределены одновременно, жестко ограничено . Ничто не помешает теоретическому компилятору присваивать значения этих констант для поддержки более 10 10 10 объектов, но с теоретической точки зрения наличие любой границы, независимо от ее размера, означает, что что-то не бесконечно.Программа может хранить неограниченное количество информации в стеке, если ничто, выделенное в стеке, никогда не получит свой адрес ; таким образом, можно получить программу на C, способную выполнять некоторые вещи, которые не могут быть выполнены любым конечным автоматом любого размера. Таким образом, даже если (или, возможно, потому что) доступ к переменным стека намного более ограничен, чем доступ к динамически размещаемым переменным, он превращает C из конечного автомата в автомат с нажатием на кнопку вниз.
Однако существует еще одна потенциальная проблема: требуется, чтобы, если программа проверяла базовые последовательности значений символов фиксированной длины, связанные с двумя указателями на разные объекты, эти последовательности должны быть уникальными. Потому что есть толькоUСЧАСA R _ MA Xев я ге о е( У п ев я гн д д c h a r ∗ ) возможные последовательности символьных значений, любая программа, которая создала количество указателей на отдельные объекты, превышающие его, не могла бы соответствовать стандарту C, если код когда-либо проверял последовательность символов, связанных с этими указателями . Однако в некоторых случаях для компилятора будет возможно определить, что ни один код не собирался проверять последовательность символов, связанных с указателем. Если бы каждый символ «char» был способен удерживать любое конечное целое число, а память машины представляла собой счетно-бесконечную последовательность целых чисел [с помощью машины Тьюринга с неограниченной лентой, можно было бы эмулировать такую машину, хотя она была бы очень медленной], тогда было бы действительно возможно сделать C тьюрингово-полным языком.
источник
С (дополнительной) библиотекой потоков C11 можно сделать полную реализацию Turing при неограниченной глубине рекурсии.
Создание нового потока дает второй стек; Для полноты по Тьюрингу достаточно двух стеков. Один стек представляет то, что слева от головы, другой стек, что справа.
источник
Я думаю, что все по Тьюрингу завершено : мы можем написать программу, которая имитирует UTM, используя этот трюк (я быстро написал код вручную, поэтому, возможно, есть некоторые синтаксические ошибки ... но я надеюсь, что в логике нет (серьезных) ошибок :-)
head
Будет указатель наcell_t
структуруstacker
функцию, когда она читает последний символ входной ленты (используя readchar)РЕДАКТИРОВАТЬ: подумав немного, есть проблема с указателями ...
если каждый вызов рекурсивной функции
stacker
может поддерживать действительный указатель на переменную, определенную локально в вызывающей программе, тогда все в порядке ; в противном случае мой алгоритм не может содержать действительный список с двумя связями для неограниченной рекурсии (и в этом случае не вижу способа использовать рекурсию для симуляции неограниченного хранилища с произвольным доступом).источник
stacker
newcell
stacker
sizeof(cell_t)
Пока у вас есть неограниченный размер стека вызовов, вы можете кодировать вашу ленту в стеке вызовов и осуществлять произвольный доступ к ней, перематывая указатель стека, не возвращаясь из вызовов функций.Редактировать : Если вы можете использовать только оперативную память , которая является конечной, эта конструкция больше не работает, так что смотрите ниже.
Однако весьма сомнительно, почему ваш стек может быть бесконечным, а внутренняя память - нет.
Так что на самом деле я бы сказал, что вы даже не можете распознать все обычные языки, так как количество состояний ограничено (если вы не учитываете трюк с перемоткой стека для использования бесконечного стека).Я бы даже предположил, что число языков, которые вы можете распознать, конечно (даже если сами языки могут быть бесконечными, например,a*
это нормально, ноb^k
работает только для конечного числаk
s).РЕДАКТИРОВАТЬ : Это не так, поскольку вы можете закодировать текущее состояние в дополнительных функциях, чтобы вы могли по-настоящему распознать ВСЕ обычные языки.
Скорее всего, вы можете получить все языки типа 2 по той же причине, но я не уверен, что удастся ли вам поместить и состояние, и стековое согласие в стек вызовов. Но, в общем, вы можете эффективно забыть об оперативной памяти, поскольку вы всегда можете масштабировать размер автомата, чтобы ваш алфавит превышал пропускную способность оперативной памяти. Так что, если бы вы могли смоделировать TM только со стеком, Type-2 был бы равен Type-0, не так ли?
источник
Я однажды подумал об этом и решил попробовать реализовать неконтекстно-свободный язык, используя ожидаемую семантику; Ключевой частью реализации является следующая функция:
По крайней мере, я думаю, что это работает. Впрочем, возможно, я совершаю фундаментальную ошибку.
Фиксированная версия:
источник
it = *it
должна быть замененаit = * (void **) it
, так как в противном случае*it
имеет типvoid
.В соответствии с ответом @ supercat:
Утверждения о неполноте C, кажется, сосредоточены вокруг того, что разные объекты должны иметь разные адреса, а набор адресов предполагается конечным. Как пишет @supercat
unsigned char*
sizeof(unsigned char*)
sizeof(unsigned char*)
На этом этапе следует проверить, что стандарт Си действительно допускает это.
sizeof
источник
uintptr_t p = (uintptr_t)sizeof(void*)
(помещая \ omega в нечто, содержащее целые числа без знака). Не знаю. Мы можем избежать определения результата равным 0 (или любому другому числу).uintptr_t
тоже должно быть бесконечным. Имейте в виду, этот тип является необязательным - но если у вас есть бесконечное количество различных значений указателя, то онsizeof(void*)
также должен быть бесконечным, поэтомуsize_t
должен быть бесконечным. Однако мое возражение по поводу сокращения по модулю не столь очевидно - оно вступает в действие только при переполнении, но если вы разрешаете бесконечные типы, они могут никогда не переполниться. Но с другой стороны, каждый тип имеет минимальное и максимальное значения, что, насколько я могу судить, подразумевает, что ониUINT_MAX+1
должны переполняться.size_t
типа указателя, чтобы быть ℕ ∪ {ω}. Это избавляет от проблемы мин / макс. Проблема с семантикой переполнения все еще остается. Какая должна быть семантика,uint x = (uint)ω
мне не ясно. Опять же, мы могли бы случайно взять 0, но это выглядит немного уродливо.