Чи є насправді С-Тьюрінг повним?


39

Я намагався пояснити комусь, що C є Тьюрінгом, і зрозумів, що я насправді не знаю, чи справді це технічно Тюрінг-завершений. (З як у абстрактній семантиці, а не як у реальній реалізації.)

Наскільки я можу сказати, "очевидний" відповідь (приблизно: він може адресувати довільний об'єм пам'яті, тому він може імітувати машину оперативної пам'яті, так що це Turing-завершений) насправді не є правильним, начебто стандарт C дозволяє щоб розмір_t був довільно великим, він повинен бути зафіксований на деякій довжині, і незалежно від того, якою довжиною він фіксується, він все ще є кінцевим. (Іншими словами, хоча ви могли, враховуючи довільну зупинку машини Тьюрінга, вибрати довжину size_t такою, щоб вона працювала «належним чином», немає способу вибрати довжину size_t такою, щоб усі машини, що зупиняють Turing, працюватимуть належним чином)

Отже: чи є Т99 Тьюрінг повним?


3
Що таке "абстрактна семантика" С? Вони десь визначені?
Yuval Filmus

4
@YuvalFilmus - Дивіться, наприклад, тут , тобто C, як визначено в стандарті, на відміну від, наприклад, "так це робить gcc".
TLW

1
існує "технічність", що сучасні комп'ютери не мають нескінченної пам'яті, як TM, але все ще вважаються "універсальними комп'ютерами". і зауважте, що мови програмування в їх "семантиці" насправді не передбачають обмеженої пам'яті, за винятком того, що всі їх реалізації , звичайно, обмежені в пам'яті. дивіться, наприклад, чи працює наш ПК як машина Тьюрінга . так чи інакше, усі "основні" мови програмування є Тьюрінгом завершеними.
vzn

2
C (як і машини Тьюрінга) не обмежується використанням внутрішньої пам'яті комп'ютера для своїх обчислень, тому це насправді не є коректним аргументом проти повноти
Цюрінга

@reinierpost - це все одно, що сказати, що телетайп є сапієнтом. Це говорить про те, що "C + зовнішній TM-еквівалент" є повним Тьюрінга, а не те, що C є Тьюрінгом.
TLW

Відповіді:


34

Я не впевнений, але думаю, що відповідь - ні, з досить тонких причин. Я запитав теоретичні інформатики кілька років тому і не отримав відповіді, що виходить за рамки того, що я тут представлю.

У більшості мов програмування ви можете імітувати машину Тюрінга:

  • моделювання скінченного автомата з програмою, яка використовує кінцевий об'єм пам'яті;
  • моделювання стрічки з парою пов'язаних списків цілих чисел, що представляють вміст стрічки до і після поточного положення. Переміщення вказівника означає перенесення заголовка одного зі списків на інший список.

Конкретна реалізація, що працює на комп’ютері, втратила б пам’ять, якщо стрічка вийшла занадто довгою, але ідеальна реалізація могла б сумлінно виконати програму машини Тьюрінга. Це можна зробити за допомогою ручки та паперу або придбати комп’ютер з більшою кількістю пам’яті та компілятор, орієнтований на архітектуру з більшою кількістю бітів на слово тощо, якщо у програми колись не вистачає пам’яті.

У C це не працює, оскільки неможливо мати зв’язаний список, який може зростати назавжди: завжди існує певна обмеженість у кількості вузлів.

Щоб пояснити чому, спершу потрібно пояснити, що таке реалізація на C. С насправді сімейство мов програмування. Стандарт ISO C (точніше, конкретна версія цього стандарту) визначає (з рівнем формальності, який дозволяє англійська) синтаксис та семантику сімейства мов програмування. C має багато невизначеної поведінки та визначеної реалізацією поведінки. "Реалізація" C кодифікує всю поведінку, визначену реалізацією (перелік речей, які потрібно кодифікувати, є в додатку J до C99). Кожна реалізація C є окремою мовою програмування. Зауважте, що значення слова "реалізація" є дещо своєрідним: що це насправді означає мовний варіант, може бути кілька різних програм компілятора, які реалізують один і той же варіант мови.

2CHAR_BITt2CHAR_BIT×sizeof(t)

2CHAR_BIT×sizeof(void*)

Ці значення CHAR_BITта sizeof(void*)видимі, тому якщо у вас не вистачає пам'яті, ви не можете просто відновити роботу програми з більшими значеннями для цих параметрів. Ви б запускали програму під іншою мовою програмування - іншою реалізацією на C.

n×2CHAR_BIT×sizeof(void*)n

C не нав'язує безпосередньо максимальну глибину рекурсії. Дозволено мати максимум, але також не можна. Але як ми спілкуємося між викликом функції та його батьківським? Аргументи не корисні, якщо вони адресовані, тому що це побічно обмежувало б глибину рекурсії: якщо у вас є функція, int f(int x) { … f(…) …}то всі входження xв активних кадрах fмають свою власну адресу, і тому кількість вкладених викликів обмежується числом можливих адрес для x.

Програма змінного струму може використовувати не адресований сховище у вигляді registerзмінних. "Нормальні" реалізації можуть мати лише невелику, обмежену кількість змінних, що не мають адреси, але теоретично реалізація може забезпечити необмежену кількість registerпам'яті. У такій реалізації ви можете робити необмежену кількість рекурсивних викликів функції, доки є її аргументом register. Але оскільки аргументи є register, ви не можете зробити вказівник на них, і тому вам потрібно копіювати їх дані навколо: ви можете передавати лише обмежений обсяг даних, а не структуру даних довільного розміру, яка складається з покажчиків.

З необмеженою глибиною рекурсії та обмеженням того, що функція може отримувати дані лише від свого прямого абонента ( registerаргументи) та повертати дані своєму прямому виклику (повернене значення функції), ви отримуєте потужність детермінованих автоматичних натискань .

Я не можу знайти спосіб піти далі.

(Звичайно, ви можете змусити програму зберігати вміст стрічки зовнішньо через функції введення / виводу файлів. Але тоді ви не запитаєте, чи є C повним Тюрінгом, але чи C безмежною системою зберігання є Тюрінг-повною, відповідь - нудне "так". Ви можете також визначити, що сховище буде оракулом Тюрінга, дзвінок  fopen("oracle", "r+"), fwriteпочатковий вміст стрічки до нього та freadповернення остаточного вмісту стрічки.)


Не відразу зрозуміло, чому набір адрес повинен бути скінченним. Я написав кілька думок у відповідь на запитання, яке ви посилаєте: cstheory.stackexchange.com/a/37036/43393
Олексій Б.

4
Вибачте, але за тією ж логікою взагалі немає мов програмування, повних Тюрінга. Кожна мова має явне або неявне обмеження на адресний простір. Якщо ви створюєте машину з нескінченною пам'яттю, то покажчики випадкового доступу, очевидно, також матимуть нескінченну довжину. Тому, якщо така машина з'явиться, доведеться запропонувати набір інструкцій для послідовного доступу до пам'яті разом з API для мов високого рівня.
IMil

14
@IMil Це неправда. Деякі мови програмування не мають обмежень щодо адресного простору, навіть неявно. Щоб зробити очевидний приклад, універсальна машина Тьюрінга, де початковий стан стрічки утворює програму, є повною мовою програмування Тьюрінга. Багато мов програмування, які фактично використовуються на практиці, мають однакову властивість, наприклад, Lisp та SML. Мова не повинна мати поняття "покажчик випадкового доступу". (продовж.)
Жил 'SO- перестань бути злим'

11
@IMil (продовження) Реалізації зазвичай роблять для продуктивності, але ми знаємо, що реалізація, що працює на певному комп'ютері, не є повною Тюрінгом, оскільки обмежена розміром пам'яті комп'ютера. Але це означає, що реалізація не реалізує всю мову, а лише підмножину (програм, що працюють в N байтах пам'яті). Ви можете запустити програму на комп’ютері, і якщо у неї не вистачить пам’яті, перемістіть її на більший комп’ютер і так далі назавжди або поки вона не зупиниться. Це було б дійсним способом реалізації всієї мови.
Жил 'ТАК - перестань бути злим'

6

Додавання C99 до va_copyAPI різноманітного аргументу може дати нам задній шлях до повноти Тьюрінга. Оскільки стає можливим повторення через список різноманітних аргументів більше одного разу у функції, відмінній від тієї, яка спочатку отримала аргументи, va_argsможе використовуватися для реалізації покажчика без вказівника.

Звичайно, реальна реалізація API різноманітного аргументу, ймовірно, десь матиме вказівник, але в нашій абстрактній машині це може бути реалізовано за допомогою магії.

Ось демонстрація, що реалізує 2-стековий автоматичний віджимання з довільними правилами переходу:

#include <stdarg.h>
typedef struct { va_list va; } wrapped_stack; // Struct wrapper needed if va_list is an array type.
#define NUM_SYMBOLS /* ... */
#define NUM_STATES /* ... */
typedef enum { NOP, POP1, POP2, PUSH1, PUSH2 } operation_type;
typedef struct { int next_state; operation_type optype; int opsymbol; } transition;
transition transition_table[NUM_STATES][NUM_SYMBOLS][NUM_SYMBOLS] = { /* ... */ };

void step(int state, va_list stack1, va_list stack2);
void push1(va_list stack2, int next_state, ...) {
    va_list stack1;
    va_start(stack1, next_state);
    step(next_state, stack1, stack2);
}
void push2(va_list stack1, int next_state, ...) {
    va_list stack2;
    va_start(stack2, next_state);
    step(next_state, stack1, stack2);
}
void step(int state, va_list stack1, va_list stack2) {
    va_list stack1_copy, stack2_copy;
    va_copy(stack1_copy, stack1); va_copy(stack2_copy, stack2);
    int symbol1 = va_arg(stack1_copy, int), symbol2 = va_arg(stack2_copy, int);
    transition tr = transition_table[state][symbol1][symbol2];
    wrapped_stack ws;
    switch(tr.optype) {
        case NOP: step(tr.next_state, stack1, stack2);
        // Note: attempting to pop the stack's bottom value results in undefined behavior.
        case POP1: ws = va_arg(stack1_copy, wrapped_stack); step(tr.next_state, ws.va, stack2);
        case POP2: ws = va_arg(stack2_copy, wrapped_stack); step(tr.next_state, stack1, ws.va);
        case PUSH1: va_copy(ws.va, stack1); push1(stack2, tr.next_state, tr.opsymbol, ws);
        case PUSH2: va_copy(ws.va, stack2); push2(stack1, tr.next_state, tr.opsymbol, ws);
    }
}
void start_helper1(va_list stack1, int dummy, ...) {
    va_list stack2;
    va_start(stack2, dummy);
    step(0, stack1, stack2);
}
void start_helper0(int dummy, ...) {
    va_list stack1;
    va_start(stack1, dummy);
    start_helper1(stack1, 0, 0);
}
// Begin execution in state 0 with each stack initialized to {0}
void start() {
    start_helper0(0, 0);
}

Примітка. Якщо va_listтип масиву - це фактично приховані параметри вказівника до функцій. Тому, мабуть, було б краще змінити типи всіх va_listаргументів на wrapped_stack.


Це може спрацювати. Можливе занепокоєння полягає в тому, що він покладається на виділення необмеженої кількості автоматичних va_listзмінних stack. Ці змінні повинні мати адресу &stack, і ми можемо мати лише обмежене число. Цю вимогу можна обійти, оголосивши кожну локальну змінну register, можливо?
чі

@chi AIUI змінної не потрібно мати адресу, якщо хтось не намагається взяти адресу. Також незаконним є оголошення аргументу безпосередньо перед еліпсісом register.
feersum

За тією ж логікою, чи не intпотрібно вимагати мати обмежений зв'язок, якщо хтось не використовує зв'язану або sizeof(int)?
чи

@chi Зовсім не. Стандарт визначає як частину абстрактної семантики, що intмає значення між деякими кінцевими межами INT_MINта INT_MAX. І якщо значення intпереповнення цих обмежених, відбувається невизначена поведінка. З іншого боку, стандарт навмисно не вимагає, щоб усі об'єкти були фізично присутніми в пам'яті за певною адресою, оскільки це дозволяє оптимізувати, наприклад, зберігати об’єкт в реєстрі, зберігати лише частину об'єкта, представляючи його інакше, ніж стандарт макет або зовсім пропустити його, якщо він не потрібен.
feersum

4

Можливо, нестандартна арифметика?

Отже, видається, що проблема полягає в кінцевому розмірі sizeof(t). Однак, я думаю, я знаю, як обійтись.

Наскільки мені відомо, C не вимагає реалізації, щоб використовувати стандартні цілі числа для свого цілого типу. Тому ми могли б використовувати нестандартну модель арифметики . Тоді ми б встановили sizeof(t)деякий нестандартний номер, і тепер ми ніколи не досягнемо його за кінцеву кількість кроків. Тому довжина стрічки машин Тюрінга завжди буде меншою, ніж "максимум", оскільки максимум досягти буквально неможливо. sizeof(t)просто не є числом у регулярному розумінні цього слова.

Це одна технічна річ: теорема Тенненбаума . У ньому зазначається, що єдиною моделлю арифметики Пеано є стандартна, що, очевидно, не зробить. Однак, наскільки я знаю, C не вимагає реалізації для використання типів даних, які задовольняють аксіоми Peano, а також не вимагає обчислення реалізації, тому це не повинно бути проблемою.

Що має статися, якщо ви спробуєте вивести нестандартне ціле число? Ну, ви можете представляти будь-яке нестандартне ціле число за допомогою нестандартного рядка, тому просто потійте цифри з передньої частини цього рядка.


Як виглядатиме реалізація?
reinierpost

@reinierpost Я здогадуюсь, що це буде представляти дані за допомогою деякої лічильної нестандартної моделі ПА. Він обчислює арифметичні операції з використанням ступеня ПА . Я думаю, що будь-яка така модель повинна забезпечити дійсну C-реалізацію.
PyRulez

На жаль, це не працює. sizeof(t)само по собі є значенням типу size_t, тому це натуральне ціле число між 0 і SIZE_MAX.
Жил "ТАК - перестань бути злим"

@Gilles SIZE_MAX також був би нестандартним природним явищем.
PyRulez

Це цікавий підхід. Зверніть увагу, що вам також знадобиться, наприклад, intptr_t / uintptr_t / ptrdiff_t / intmax_t / uintmax_t, щоб бути нестандартним. У C ++ це призведе до гарантій прогресу вперед ... не впевнений щодо C.
TLW

0

IMO, сильне обмеження полягає в тому, що адресний простір (через розмір вказівника) є кінцевим, і це неможливо відновити.

Можна припустити, що пам'ять можна "поміняти на диск", але в якийсь момент інформація про адресу сама перевищить розмір адреси.


Хіба це не головний пункт прийнятої відповіді? Я не думаю, що це додає нічого нового до відповідей на це питання 2016 року.
чі

@chi: ні, прийнята відповідь не згадує заміну на зовнішню пам’ять, що, як можна вважати, є вирішенням проблеми.
Ів Дауст

-1

На практиці ці обмеження не мають значення для повноти Тьюрінга. Справжня вимога - дозволити стрічці бути довільною довгою, а не нескінченною. Це створило б проблему зупинки іншого типу (як Всесвіт "обчислює" стрічку?)

Це так само хибно, як сказати "Python не є Тюрінг повним, тому що ви не можете зробити список нескінченно великим".

[Редагувати: дякую містеру Вітлідж за пояснення, як редагувати.]


7
Я не думаю, що це відповідає на питання. Питання вже передбачило цю відповідь і пояснило, чому вона не є дійсною: "хоча стандарт C дозволяє розмір_t бути довільно великим, він повинен бути зафіксований на деякій довжині, і незалежно від того, яку довжину він зафіксував, він все ще обмежений ". Чи є у вас відповідь на цей аргумент? Я не думаю, що ми можемо вважати це питання як відповідне, якщо відповідь не пояснює, чому цей аргумент є неправильним (або правильним).
DW

5
У будь-який момент часу значення типу size_tє кінцевим. Проблема полягає в тому, що ви не можете встановити обмеження, size_tщо є дійсним протягом усього обчислення: для будь-яких зв'язаних програм програма може переповнювати його. Але мова С зазначає, що існує обмеження для size_t: за даної реалізації, вона може виростати лише до sizeof(size_t)байтів. Також будьте приємні . Сказати, що люди, які критикують вас "не можуть думати самостійно", є грубим.
Жил "ТАК - перестань бути злим"

1
Це правильна відповідь. Машині, що обертається, не потрібна нескінченна стрічка, їй потрібна стрічка «довільно довга». Тобто ви можете припустити, що стрічка триває стільки, скільки потрібно для завершення обчислення. Ви також можете припустити, що ваш комп'ютер має стільки пам'яті, скільки потрібно. Нескінченна стрічка абсолютно не потрібна, тому що будь-які обчислення, які зупиняються протягом обмеженого часу, не можуть використовувати нескінченну кількість стрічки.
Джеффрі Л Уітлідж

Ця відповідь свідчить про те, що для кожної ТМ ви можете написати реалізацію C з достатньою довжиною вказівника для її моделювання. Однак неможливо написати одну реалізацію C, яка може імітувати будь-яку TM. Таким чином, специфікація забороняє будь-яку конкретну реалізацію мати T-завершення. Він також не є T-завершеним, оскільки фіксується довжина вказівника.

1
Це ще одна правильна відповідь, яку навряд чи видно через недієздатність більшості людей у ​​цій громаді. Тим часом прийнята відповідь неправдива, а її коментарний розділ охороняється модераторами, які видаляють критичні коментарі. До побачення, cs.stackexchange.
xamid

-1

Знімний носій дозволяє нам обійти проблему без обмеженої пам'яті. Можливо, люди подумають, що це зловживання, але я думаю, що це нормально і взагалі неминуче.

Виправити будь-яку реалізацію універсальної машини Тьюрінга. Для стрічки ми використовуємо знімні носії. Коли головка закінчується в кінці або на початку поточного диска, машина пропонує користувачеві вставити наступний або попередній. Ми можемо використовувати спеціальний маркер для позначення лівого кінця імітованої стрічки або мати стрічку, яка не обмежена в обох напрямках.

Ключовим моментом тут є те, що все, що програма C повинна робити, є кінцевим. Комп'ютеру потрібно лише достатньо пам'яті для імітації автомата, і size_tвін повинен бути достатньо великим, щоб можна було вирішити той (насправді досить невеликий) об'єм пам'яті та на дисках, які можуть мати будь-який фіксований обмежений розмір. Оскільки користувачеві пропонується лише вставити наступний чи попередній диск, нам не потрібно необмежено великих чисел, щоб сказати "Будь ласка, введіть номер диска 123456 ..."

Я припускаю, що основне заперечення, ймовірно, стосується залучення користувача, але це, здається, неминуче в будь-якій реалізації, оскільки, схоже, немає іншого способу реалізації необмеженої пам'яті.


3
Я заперечую, що, якщо визначення С не вимагає такого необмеженого зовнішнього сховища, то це не може сприйматися як доказ повноти Тьюрінга. (ISO 9899, ​​звичайно, не вимагає, щоб це було написано для реальної інженерії.) Що мене стосується, це те, що, якщо ми приймемо це, за допомогою подібних міркувань ми можемо стверджувати, що DFA є Тюрінг завершеним, оскільки вони можуть бути використані загнати голову над стрічкою (зовнішнє сховище).
чи

@chi Я не бачу, як слід аргумент DFA. Вся суть DFA полягає в тому, що він має лише доступ для читання до сховища. Якщо ви дозволите йому «проїхати головою по стрічці», це не точно машина Тьюрінга?
Девід Річербі

2
Дійсно, я тут трохи пощупаю. Суть у тому, чому це нормально додати "стрічку" до C, нехай C імітує DFA, і використовуємо цей факт, щоб стверджувати, що C Turing завершений, коли ми не можемо зробити те ж саме в DFA? Якщо C не має можливості самостійно реалізовувати необмежену пам'ять, то це не слід вважати Тьюрінгом повним. (Я б все-таки називав це "морально". Тюрінг завершений, принаймні, оскільки межі настільки великі, що на практиці вони не мають значення в більшості випадків). Я думаю, що для остаточного вирішення питання потрібна буде сувора формальна специфікація C (стандарт ISO не вистачає)
chi

1
@chi Це нормально, оскільки C включає підпрограми вводу / виводу файлів. DFA - ні.
Девід Річербі

1
C не повністю уточнює, що роблять ці підпрограми - більшість їх семантики визначено реалізацією. Реалізація змінного струму не потрібна для зберігання вмісту файлу, наприклад, я думаю, що він може вести себе так, як якщо б кожен файл був "/ dev / null", так би мовити. Також не потрібно зберігати необмежений обсяг даних. Я б сказав, що ваш аргумент правильний, коли ми розглядаємо, що робить переважна більшість реалізацій C, і узагальнюючи цю поведінку на ідеальній машині. Якщо ми чітко покладаємось на визначення С, тільки, забуваючи практику, я не думаю, що це справедливо.
чи

-2

Вибирайте, size_tщоб бути нескінченно великим

Ви могли вибрати size_tнескінченно великі. Звичайно, реалізувати таку реалізацію неможливо. Але це не дивно, враховуючи скінченний характер світу, в якому ми живемо.

Практичні наслідки

Але навіть якби можна було реалізувати таку реалізацію, виникали б практичні питання. Розглянемо наступне твердження C:

printf("%zu\n",SIZE_MAX);

SIZE_MAXSIZE_MAXO(2size_t)size_tSIZE_MAXprintf

На щастя, для наших теоретичних цілей я не зміг знайти жодної вимоги в специфікації, згідно з якою гарантії printfприпиняються на всі вхідні дані. Отже, наскільки мені відомо, ми тут не порушуємо специфікацію C.

Про обчислювальну повноту

Залишається довести, що наша теоретична реалізація - Тьюрінг завершений . Це ми можемо показати, застосувавши "будь-яку машину Тюрінга".

Більшість із нас, мабуть, реалізували машину Тюрінга як шкільний проект. Я не буду наводити подробиці конкретної реалізації, але ось загальновживана стратегія:

  • Кількість станів, кількість символів та таблиця переходів стану фіксуються для будь-якої машини. Таким чином, ми можемо представляти стани та символи у вигляді чисел, а таблиця переходів стану як двовимірний масив.
  • Стрічка може бути представлена ​​у вигляді пов'язаного списку. Ми можемо використовувати один подвійний зв'язаний список або два односхилених списки (по одному для кожного напрямку від поточної позиції).

Тепер давайте розберемося, що потрібно для реалізації такої реалізації:

  • Здатність представляти деякий фіксований, але довільно великий набір чисел. Щоб представити будь-яке довільне число, ми також вирішимо MAX_INTбути нескінченними. (Крім того, ми могли б використовувати інші об'єкти для представлення станів та символів.)
  • Можливість побудови довільно великого пов'язаного списку для нашої стрічки. Знову ж таки, немає обмежень щодо розміру. Це означає, що ми не можемо створити цей список наперед, оскільки ми витратили б назавжди лише на побудову своєї стрічки. Але ми можемо нарощувати цей список поступово, якщо використовувати динамічне розподіл пам’яті. Ми можемо використовувати malloc, але є ще трохи, що ми повинні врахувати:
    • Специфікація C дозволяє mallocвийти з ладу, якщо, наприклад, наявна пам'ять вичерпана. Тож наша реалізація є справді універсальною лише тоді, коли mallocніколи не провалюється.
    • Однак якщо наша реалізація працює на машині з нескінченною пам’яттю, тоді не потрібно mallocвідмовлятися. Без порушення стандарту С наша реалізація гарантує, що mallocніколи не вийде з ладу.
  • Можливість розмежування покажчиків, пошук елементів масиву та доступ до членів зв’язаного вузла списку.

Отже, наведений вище перелік - це те, що необхідно для впровадження машини Тьюрінга в нашій гіпотетичній реалізації C. Ці функції повинні припинитись. Однак, будь-що інше може бути дозволено не припиняти (якщо цього не вимагає стандарт). Сюди входить арифметика, ІО тощо.


6
Що було б printf("%zu\n",SIZE_MAX);надрукувати на такій реалізації?
Руслан

1
@ Руслан, така реалізація неможлива, як і неможлива реалізація машини Тьюрінга. Але якби така реалізація була можливою, я думаю, вона надрукувала б десяткове представлення нескінченно великої кількості - імовірно, нескінченного потоку десяткових цифр.
Натан Девіс

2
@NathanDavis Можлива реалізація машини Тьюрінга. Хитрість полягає в тому, що вам не потрібно будувати нескінченну стрічку - ви просто будуєте використану частину стрічки поступово, як потрібно.
Жил "ТАК - перестань бути злим"

2
@Gilles: У цій кінцевій всесвіті, в якій ми живемо, неможливо реалізувати машину Тюрінга.
gnasher729

1
@NathanDavis Але якщо ви це зробите, то ви змінилися sizeof(size_t)(або CHAR_BITS). Ви не можете відновитись із нового штату, вам доведеться почати заново, але виконання програми може бути іншим зараз, коли ці константи відрізняються
Жил "SO- перестань бути злим"

-2

Основним аргументом тут було те, що розмір size_t є кінцевим, хоча може бути нескінченно великим.

Для цього є рішення, хоча я не впевнений, чи збігається це з ISO C.

Припустимо, у вас машина з нескінченною пам'яттю. Таким чином, ви не обмежені для розміру вказівника. Ви все ще маєте свій тип size_t. Якщо ви запитаєте мене, що таке sizeof (size_t), відповідь буде просто sizeof (size_t). Наприклад, якщо ви запитуєте, чи не перевищує 100, відповідь - так. Якщо ви запитаєте, що таке sizeof (size_t) / 2, як ви могли здогадатися, відповідь все ще є sizeof (size_t). Якщо ви хочете роздрукувати його, ми можемо домовитись про вихід. Різниця цих двох може бути NaN тощо.

Підсумок полягає в тому, що розслаблення умови для size_t мають обмежений розмір не порушить жодної програми, яка вже існує.

PS Виділення розміру пам'яті (size_t) все ще можливо, вам потрібен лише лічильний розмір, так що скажімо, ви берете всі рівнини (або подібний трюк).


1
"Різниця цих двох може бути NaN". Ні, цього не може бути. Немає такої речі, як NaN цілого типу в C.
TLW

Відповідно до стандарту, sizeofмає повернути a size_t. Отже, ви повинні вибрати якесь конкретне значення.
Драконіс

-4

Так.

1. Цитована відповідь

Як реакція на велику кількість посилань на мої (та інші) правильні відповіді - порівняно з тривожно високим схваленням помилкових відповідей - я шукав менш теоретично глибоке альтернативне пояснення. Я знайшов цього . Я сподіваюсь, що він охоплює деякі тут поширені помилки, щоб проявити трохи більше розуміння. Істотна частина аргументу:

[...] Його аргумент такий: припустимо, хтось написав задану програму, що закінчується, яка може вимагати під час її виконання до деякої кількості довільної кількості пам’яті. Не змінюючи цю програму, можна після цього впровадити частину комп'ютерного обладнання та його компілятор C, які забезпечують достатню кількість пам’яті для розміщення цього обчислення. Це може зажадати розширення ширини знаків (через CHAR_BITS) та / або покажчиків (через size_t), але програму не потрібно змінювати. Оскільки це можливо, C справді є Turing-Complete для завершення програм.

Складна частина цього аргументу полягає в тому, що він працює лише при розгляді програми, що припиняється. Програми, що припиняють, мають це приємне властивість: вони мають статичну верхню межу своєї вимоги до зберігання, яку можна визначити експериментально, запустивши програму над потрібним входом із збільшенням розміру пам’яті, поки вона не «підходить».

Причина, чому мене ввели в оману в моїй думці, це те, що я розглядаю ширший клас «корисних» непереривних програм [...]

Коротше кажучи, оскільки для кожної обчислюваної функції існує рішення мовою С (через необмеженість верхніх меж), кожна обчислювана проблема має програму C, таким чином, C є повним Turing.

2. Моя оригінальна відповідь

Існують широкі плутанини між математичними поняттями в теоретичній інформатиці (такі як Тьюрінг-повнота) та їх застосуванні в реальному світі, тобто технікою в практичній інформатиці. Тюрінг-повнота не є властивістю фізично існуючих машин або будь-якої в обмеженому простором часу моделі. Це просто абстрактний об'єкт, що описує властивості математичних теорій.

C99 є повним Тьюрінгом незалежно від обмежень на основі реалізації, як і практично будь-яка інша поширена мова програмування, оскільки він здатний виражати функціонально повний набір логічних сполучників і, в принципі, має доступ до необмеженого обсягу пам'яті. Люди вказували, що C обмежує доступ до випадкової пам'яті явно обмеженим, але цього нічого не можна обійти, оскільки це додатково зазначені обмеження в стандарті C, тоді як повнота Тьюрінга вже без них :

Ось дуже основна річ про логічні системи, яких повинно вистачити для неконструктивного доказу. Розглянемо обчислення з деякими схемами та правилами аксіом, таким чином, що набір логічних послідовностей дорівнює X. Тепер, якщо додати деякі правила або аксіоми, набір логічних наслідків зростає, тобто повинен бути надмножиною X. Ось чому, наприклад, , модальна логіка S4 належним чином міститься в S5. Аналогічно, коли у вас є підвищена специфікація, яка завершена Тьюрінгом, але ви додаєте деякі обмеження зверху, це не запобігає жодним із наслідків у X, тобто повинен бути спосіб обійти всі обмеження. Якщо ви хочете мову, що не закінчується Тюрінгом, обчислення повинно бути скороченим, а не розширеним. Розширення, які стверджують, що щось було б неможливо, але насправді є, лише додають неузгодженості. Ці невідповідності стандарту С можуть не мати жодних практичних наслідків, хоча так само, як Тюрінг-повнота не пов'язана з практичним застосуванням.

Моделювання довільних чисел на основі глибини рекурсії (тобто це ; з можливістю підтримки декількох чисел за допомогою планування / псевдопотоків; немає теоретичного обмеження глибини рекурсії в С ), або використання файлового зберігання для імітації необмеженої пам'яті програми ( ідея ) ймовірно, лише дві з нескінченних можливостей конструктивно довести Тьюрінг-повноту C99. Слід нагадати, що для обчислюваності складність часу та простору не має значення. Зокрема, припускати обмежене середовище для фальсифікації повноти Тьюрінга - це лише кругові міркування, оскільки це обмеження виключає всі проблеми, що перевищують передбачувану межу складності.

( ПРИМІТКА . Цю відповідь я написав лише для того, щоб люди не зупинялися на здобуття математичної інтуїції через якесь обмежене мислення, орієнтоване на додатки. Дуже шкода, що більшість учнів прочитають неправдиву прийняту відповідь через те, що її авансовують на основі основні недоліки міркувань, щоб більше людей поширювали такі помилкові переконання. Якщо ви спростуєте цю відповідь, ви просто частина проблеми.)


4
Я не дотримуюся твого останнього пункту. Ви стверджуєте, що додавання обмежень збільшує виразність, але це явно не відповідає дійсності. Обмеження може лише зменшити виражальну силу. Наприклад, якщо ви взяли C і додали обмеження, що жодна програма не може отримати доступ до більш ніж 640 кб пам’яті (будь-якого виду), ви перетворили це на фантазійний кінцевий автомат, який явно не є повним Тьюрінгом.
Девід Річербі

3
Якщо у вас фіксований обсяг пам’яті, ви не зможете імітувати нічого, що потребує більше ресурсів, ніж у вас є. Існує лише безліч конфігурацій, в яких ваша пам’ять може бути, а це означає, що ви можете зробити лише безліч кінцевих речей.
Девід Річербі

2
Я не розумію, чому ви посилаєтесь на "фізично існуючі машини". Зауважимо, що повнота Тьюрінга є властивістю математичної обчислювальної моделі, а не фізичних систем. Я погодився б, що жодна фізична система, будучи кінцевим об'єктом, не може наблизитися до потужності машин Тьюрінга, але це не має значення. Ми все ще можемо взяти будь-яку мову програмування, розглянути математичне визначення її семантики та перевірити, чи є цей математичний об'єкт Тюрінга завершеним. Гра життя Конвея є Тюрінг потужною, навіть якщо немає можливої ​​фізичної реалізації.
чі

2
@xamid Якщо у вас є сумніви щодо політики модерації на цьому веб-сайті, перейдіть на сторінку Computer Science Meta . До тих пір, будь ласка , не буде добре . Вербальне зловживання з боку інших не буде терпіти. (Я видалив усі коментарі, які не стосуються предмету, що знаходиться у вас.)
Рафаель

2
Ви кажете, що зміна ширини вказівника не змінить програму, але програми можуть зчитувати ширину вказівника і робити все, що хочуть, із цим значенням. Те саме для CHAR_BITS.
Драконіс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.