Чому * оголошення * даних і функцій необхідне мовою C, коли визначення записується в кінці вихідного коду?


15

Розглянемо наступний код "С":

#include<stdio.h>
main()
{   
  printf("func:%d",Func_i());   
}

Func_i()
{
  int i=3;
  return i;
}

Func_i()визначається в кінці вихідного коду, і перед його використанням не подається декларація main(). У той самий час , коли компілятор бачить Func_i()в main(), він виходить з main()і дізнається Func_i(). Компілятор якимось чином знаходить повернене значення Func_i()і надає його printf(). Я також знаю , що компілятор не може знайти тип повертається з Func_i(). Це, за умовчанням приймає (здогадок?) В тип повертається з Func_i()бути int. Тобто якщо код мав, float Func_i()тоді компілятор видав би помилку: Конфліктуючі типи дляFunc_i() .

З наведеної дискусії ми бачимо, що:

  1. Компілятор може знайти значення, яке повертає Func_i().

    • Якщо компілятор може знайти значення, яке повертається, Func_i()вийшовши з main()і шукаючи вихідний код, то чому він не може знайти тип Func_i (), про який явно згадується.
  2. Компілятор повинен знати, що Func_i()це тип float - тому він дає помилку конфліктуючих типів.

  • Якщо компілятор знає, що Func_iце тип float, то чому він все ще вважає Func_i()тип int і видає помилку конфліктуючих типів? Чому б не зробити так, Func_i()щоб вони були типу float.

У мене те ж саме сумніви у декларації змінної . Розглянемо наступний код "С":

#include<stdio.h>
main()
{
  /* [extern int Data_i;]--omitted the declaration */
  printf("func:%d and Var:%d",Func_i(),Data_i);
}

 Func_i()
{
  int i=3;
  return i;
}
int Data_i=4;

Компілятор видає помилку: "Data_i" незадекларовано (перше використання в цій функції).

  • Коли компілятор бачить Func_i(), він спускається до вихідного коду, щоб знайти значення, повернене Func_ (). Чому компілятор не може зробити те саме для змінної Data_i?

Редагувати:

Я не знаю деталей внутрішньої роботи компілятора, асемблера, процесора тощо. Основна ідея мого питання полягає в тому, що якщо я скажу (записую) повернене значення функції у вихідному коді, нарешті, після використання цієї функції, тоді мова "С" дозволяє комп'ютеру знайти це значення без помилок. Тепер чому комп'ютер не може знайти тип подібним чином. Чому тип Data_i неможливо знайти, як було знайдено зворотне значення Func_i (). Навіть якщо я використовую extern data-type identifier;оператор, я не кажу про значення, яке слід повернути цим ідентифікатором (функцією / змінною). Якщо комп'ютер може знайти це значення, то чому він не може знайти тип. Навіщо нам взагалі потрібна передова декларація?

Дякую.


7
Компілятор не "знаходить" значення, повернене Func_i. це робиться під час виконання.
Джеймс Маклеод

26
Я не заявив, але питання ґрунтується на серйозних непорозуміннях того, як працюють компілятори, а ваші відповіді в коментарях свідчать, що ви все ще маєте подолати деякі концептуальні перешкоди.
James McLeod

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

19
@ user31782: Підсумок до питання: Чому мова X робить / вимагає Y? Тому що це вибір, який зробили дизайнери. Ви, здається, стверджуєте, що дизайнери однієї з найуспішніших мов коли-небудь повинні були робити різні вибори десятиліття тому, а не намагатися зрозуміти ці варіанти в контексті, який вони робили. Відповідь на ваше запитання: Навіщо нам потрібна декларація? дано: Тому що C використовує компілятор з одним проходом. Найпростіша відповідь на більшість запитань, що надаються,
Містер Міндор

4
@ user31782 Ви дуже, дуже хочете прочитати книгу дракона, щоб зрозуміти, як насправді працюють компілятори та процесори - просто неможливо перенаправити всі необхідні знання в одну відповідь (або навіть 100 на це). Чудова книга для всіх, хто зацікавився укладачами.
Во

Відповіді:


26

Тому що C - це однопрохідна , статично типова , слабо типізована , складена мова.

  1. Однопрохідний означає, що компілятор не дивиться вперед, щоб побачити визначення функції чи змінної. Оскільки компілятор не дивиться вперед, оголошення функції повинно надходити до використання функції, інакше компілятор не знає, що таке підпис його типу. Однак визначення функції може бути згодом у тому самому файлі або навіть у іншому файлі взагалі. Див. Пункт №4.

    Єдиним винятком є ​​історичний артефакт, який вважає, що незадекларовані функції та змінні мають тип "int". Сучасна практика полягає у тому, щоб уникати неявного введення тексту, завжди чітко декларуючи функції та змінні.

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

  3. Слабо типізований означає, що компілятор C автоматично генерує код для перетворення між числовими типами, не вимагаючи від програміста чітко вказати операції перетворення. Через статичне введення тексту однакові перетворення завжди будуть здійснюватися однаково кожен раз через програму. Якщо значення float перетворюється на значення int на заданому місці в коді, значення float завжди буде перетворене на значення int на цьому місці в коді. Це неможливо змінити під час виконання. Само значення може змінюватися від одного виконання програми до наступного, звичайно, і умовні висловлювання можуть змінювати, які розділи коду виконуються в якому порядку, але даний окремий розділ коду без викликів функцій або умовних умов завжди буде виконувати точне ті ж операції, коли він виконується.

  4. Скомпільований означає, що процес аналізу людського читаного вихідного коду та перетворення його в машиночитані інструкції повністю здійснюється до запуску програми. Коли компілятор компілює функцію, він не знає, з чим вона буде стикатися далі в заданому вихідному файлі. Однак після завершення компіляції (і складання, зв'язування тощо) кожна функція у готовому виконуваному файлі містить числові вказівники на функції, які вона буде викликати під час її запуску. Ось чому main () може викликати функцію далі у вихідному файлі. До моменту фактичного запуску main () він буде містити вказівник на адресу Func_i ().

    Машинний код дуже-дуже специфічний. Код додавання двох цілих чисел (3 + 2) відрізняється від коду для додавання двох поплавків (3.0 + 2.0). Вони відрізняються від додавання int до float (3 + 2.0) тощо. Компілятор визначає для кожної точки функції, яку саме операцію потрібно виконати в цій точці, і генерує код, який виконує цю точну операцію. Після цього це неможливо змінити, не перекомпілювавши функцію.

Якщо скласти всі ці поняття разом, то причина, що main () не може «бачити» далі, щоб визначити тип Func_i (), полягає в тому, що аналіз типу відбувається в самому початку процесу компіляції. У цей момент була прочитана та проаналізована лише частина вихідного файлу до визначення main (), а визначення Func_i () ще не відоме компілятору.

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

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

Крім того, будь ласка, пам’ятайте, що те, що я написав вище, стосується конкретно C.

В інших мовах компілятор може робити кілька проходів через вихідний код, і тому компілятор може підібрати визначення Func_i (), не будучи попередньо визначеним.

В інших мовах функції та / або змінні можуть бути динамічно набрані, тому одна змінна може утримуватись, або одна функція може бути передана або повернута, ціле число, поплавок, рядок, масив чи об'єкт у різний час.

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

І в інших мовах код може бути інтерпретований по одному рядку або скомпільований в байт-код і потім інтерпретований, або вчасно складений, або введений через широкий спектр інших схем виконання.


1
Дякую за відповідь "все в одному". Ваша відповідь і нікі - це те, що я хотів знати. Наприклад Func_()+1: тут під час компіляції компілятор повинен знати тип Func_i()так, щоб генерувати відповідний машинний код. Можливо, або неможливо, щоб збірка впоралася Func_()+1, зателефонувавши до типу під час виконання, або можливо, але це зробить програму повільною під час виконання програми. Я думаю, цього мені зараз достатньо.
user106313

1
Важлива деталь неявно оголошених функцій C: Вони вважаються типовими int func(...)... тобто вони беруть різноманітний список аргументів. Це означає, що якщо ви визначите функцію як, int putc(char)але забудете її оголосити, вона замість цього буде називатися як int putc(int)(тому що char, переданий через різноманітний список аргументів, рекламується int). Тож, хоча приклад ОП працював, тому що його підпис відповідав неявній декларації, зрозуміло, чому така поведінка була відсторонена (і додано відповідні попередження).
uliwitness

37

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

int Func_i();

вгорі або в заголовку, щоб допомогти компілятору.

У своїх прикладах ви використовуєте дві сумнівні особливості мови С, яких слід уникати:

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

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

Отже, у printf("func:%d",Func_i())нас є неявна заява int Func_i(). Коли компілятор досягне визначення функції Func_i() { ... }, це сумісно з типом. Але якщо ви писали float Func_i() { ... }в цей момент, у вас є int Func_i()чітка декларація і прямо заявлена float Func_i(). Оскільки обидві декларації не збігаються, компілятор видає помилку.

Усунення деяких помилок

  • Компілятор не знаходить повернене значення Func_i. Відсутність явного типу означає, що тип повернення intза замовчуванням. Навіть якщо ви це зробите:

    Func_i() {
        float f = 42.3;
        return f;
    }

    тоді тип буде int Func_i(), а повернене значення буде мовчки усіченим!

  • З часом компілятор дізнається реальний тип Func_i, але він не знає реального типу під час неявного оголошення. Тільки коли вона пізніше дійде до реальної декларації, вона зможе з’ясувати, чи був неявно оголошений тип правильним. Але в цей момент збірка для виклику функції, можливо, вже написана і не може бути змінена в моделі компіляції C.


3
@ user31782: Порядок коду має значення під час компіляції, але не під час виконання. Під час запуску програми компілятор вийшов із зображення. До часу запуску функція буде зібрана та зв’язана, її адреса буде вирішена і встромлена в заповнювач адреси виклику. (Це трохи складніше, ніж це, але це основна ідея.) Процесор може розгалужуватися вперед або назад.
Blrfl

20
@ user31782: Компілятор не друкує значення. Ваш компілятор не запускає програму !!
Гонки легкості з Монікою

1
@LightnessRacesinOrbit Я це знаю. Я помилково написав компілятор у своєму коментарі вище, тому що забув процесор імен .
користувач106313

3
@Carcigenicate C сильно впливав на мові B, яка мала лише один тип: інтегральний числовий тип ширини слів, який також можна використовувати для покажчиків. C спочатку скопіював таку поведінку, але тепер вона повністю заборонена з моменту стандарту C99. Unitробить гарний тип за замовчуванням з точки зору теорії типів, але не відповідає практичним можливостям, близьким до програмування металевих систем, для яких були розроблені B, а потім C.
амон

2
@ user31782: Компілятор повинен знати тип змінної, щоб створити правильну збірку для процесора. Коли компілятор знаходить неявне Func_i(), він негайно генерує і зберігає код для процесора, щоб перейти в інше місце, потім отримати деяке ціле число, а потім продовжити. Коли пізніше компілятор знайде Func_iвизначення, він переконається, що підписи відповідають, і якщо вони є, він розміщує збірку за Func_i()цією адресою і каже йому повернути деяке ціле число. Після запуску програми процесор виконує ці вказівки зі значенням 3.
Mooing Duck

10

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

По-друге, це не працює, як ви думаєте.

  1. Тип результату є необов’язковим у C90, не даючи одного засобу intрезультату. Це також справедливо для змінної декларації (але вам потрібно надати клас зберігання staticабо extern).

  2. Те, що робить компілятор, коли бачить Func_i, називається без попередньої декларації, припускає, що існує декларація

    extern int Func_i();

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

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


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

1
@ user31782 Якщо не було попередньої декларації Func_i, коли бачимо, що Func_i використовується в виразі виклику, він поводиться так, ніби він був extern int Func_i(). Це нікуди не виглядає.
AProgrammer

1
@ user31782, компілятор нікуди не стрибає. Він видасть код для виклику цієї функції; повернене значення визначатиметься під час виконання. Що ж, у випадку такої простої функції, яка присутня в тому ж блоці компіляції, фаза оптимізації може вбудувати функцію, але це не те, про що слід думати, розглядаючи правила мови, це оптимізація.
AProgrammer

10
@ user31782, у вас є серйозні непорозуміння щодо того, як працюють програми. Настільки серйозні, що я не думаю, що p.se - гарне місце для їх виправлення (можливо, чат, але я не намагатимусь цього зробити).
AProgrammer

1
@ user31782: Написання невеликого фрагмента та його компілювання -S(якщо ви використовуєте gcc) дозволить вам переглянути код складання, створений компілятором. Тоді ви можете мати уявлення про те, як обробляються значення обробляються під час виконання (зазвичай, використовуючи регістр процесора або деякий простір у стеку програми).
Джорджіо

7

Ви написали в коментарі:

Виконання виконується по черзі. Єдиний спосіб знайти значення, повернене Func_i (), - це вистрибнути з основного

Це неправильне уявлення: Виконання не наводиться по черзі. Компіляція проводиться по черзі, і роздільна здатність імен робиться під час компіляції, і вона розв’язує лише імена, а не значення, що повертаються.

Корисна концептуальна модель така: Коли компілятор читає рядок:

  printf("func:%d",Func_i());

він видає код, еквівалентний:

  1. call "function #2" and put the return value on the stack
  2. put the constant string "func:%d" on the stack
  3. call "function #1"

Компілятор також робить примітку у деякій внутрішній таблиці, що function #2ще не оголошена функція з іменем Func_i, яка приймає не визначене число аргументів і повертає int (за замовчуванням).

Пізніше, коли він аналізує це:

 int Func_i() { ...

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

Отже, компілятор не "шукав", Func_iколи він розбирав першу посилання. Він просто робив замітку в якійсь таблиці, і продовжував розбирати наступний рядок. І в кінці файлу, він має об'єктний файл та список стрибкових адрес.

Пізніше лінкер приймає все це і замінює всі вказівники на "функцію №2" фактичною адресою переходу, тому він видає щось на кшталт:

  call 0x0001215 and put the result on the stack
  put constant ... on the stack
  call ...
...
[at offset 0x0001215 in the file, compiled result of Func_i]:
  put 3 on the stack
  return top of the stack

Набагато пізніше, коли виконується виконуваний файл, адреса переходу вже вирішена, і комп'ютер може просто перейти на адресу 0x1215. Не потрібно шукати ім’я.

Відмова : Як я вже сказав, це концептуальна модель, а реальний світ складніший. Сьогодні компілятори та лінкери роблять усілякі шалені оптимізації. Вони навіть можуть "стрибнути вниз", щоб шукати Func_i, хоча я сумніваюся в цьому. Але мови C визначені таким чином, щоб ви могли написати суперпростий компілятор таким чином. Тож більшість часу це дуже корисна модель.


Спасибі за вашу відповідь. Неможливо компілятор випустити код:1. call "function #2", put the return-type onto the stack and put the return value on the stack?
user106313

1
(Прод . ) А також: Що робити , якщо ви написали printf(..., Func_i()+1);- компілятор повинен знати тип Func_i, тому він може вирішити , якщо вона повинна випромінювати add integerабо add floatінструкцію. Можливо, ви знайдете деякі особливі випадки, коли компілятор може продовжувати роботу без інформації про тип, але компілятор повинен працювати у всіх випадках.
nikie

4
@ user31782: Інструкції з машини, як правило, дуже прості: Додайте два 32-бітні цілочисельні регістри. Завантажте адресу пам'яті в 16-розрядний цілочисельний регістр. Перейти до адреси. Також не існує типів : Ви можете із задоволенням завантажити місце пам'яті, яке представляє 32-бітовий плаваючий номер, в регістр 32-бітових цілих чисел і виконати з ним деяку арифметику. (Це дуже рідко має сенс.) Отже, ні, ви не можете випромінювати машинний код прямо таким. Ви можете написати компілятор, який виконує всі ці речі з перевірками виконання та додатковими даними про тип на стеку. Але це не буде компілятором С.
nikie

1
@ user31782: Залежить, IIRC. floatЗначення можуть жити в реєстрі FPU - тоді інструкції взагалі не було б. Компілятор просто відслідковує, яке значення зберігається в якому регістрі під час компіляції, і видає речі типу "додати константу 1 до регістра FP X". Або він міг би жити на стеці, якщо немає вільних регістрів. Тоді буде інструкція "збільшити покажчик стека на 4", і значення буде "посилатися" як щось на зразок "покажчик стека - 4". Але всі ці речі працюють лише в тому випадку, якщо розміри всіх змінних (до і після) на стеку відомі під час компіляції.
nikie

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

5

C та ряд інших мов, які потребують декларацій, були розроблені в епоху, коли час процесора та пам'ять були дорогими. Розвиток C і Unix йшло рука об руку досить довгий час, і останні не мали віртуальної пам’яті, поки 3BSD не з'явилася в 1979 році. Без додаткового місця для роботи компілятори, як правило, були однопрохідними справами, оскільки вони не вимагають вміння зберігати деяке представлення всього файлу в пам'яті відразу.

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

На початку C (AT&T, K&R, C89) використання функції foo()перед оголошенням призвело до фактичного або неявного декларування int foo(). Ваш приклад працює, коли Func_i()оголошується, intоскільки він відповідає тому, що компілятор оголосив від вашого імені. Якщо змінити його на будь-який інший тип, це призведе до конфлікту, оскільки він більше не відповідає тому, що обрав компілятор за відсутності явної декларації. Така поведінка була видалена в C99, де використання незадекларованої функції стало помилкою.

То як щодо типів повернення?

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

Ще однією особливістю раннього С було те, що прототипи, як ми їх знаємо, зараз не існували. Ви можете оголосити тип повернення функції (наприклад, int foo()), але не його аргументи (тобто int foo(int bar)не був опцією). Це існувало тому, що, як було зазначено вище, програма завжди дотримувалась конвенції про виклик, яка могла бути визначена аргументами. Якщо ви назвали функцію з неправильним типом аргументів, це було сміття, ситуація зі сміттям.

Оскільки об'єктний код має поняття повернення, але не типу повернення, компілятор повинен знати тип повернення для обробки повернутого значення. Коли ви біжите машинні команди, це все тільки біти і процесор не піклується пам'ять , де ви намагаєтеся порівняти doubleє на самому справі intв ньому. Він просто робить те, що ви просите, і якщо ви порушите його, ви володієте обома частинами.

Розглянемо наступні біти коду:

double foo();         double foo();
double x;             int x;
x = foo();            x = foo();

Код зліва зводиться до виклику, foo()після якого слід скопіювати результат, наданий за допомогою конвенції про виклик / повернення, куди xне зберігається. Це простий випадок.

Код праворуч показує перетворення типу, і тому компіляторам потрібно знати тип повернення функції. Числа з плаваючою комою не можна скинути в пам’ять, де інший код очікує побачити, intоскільки не відбувається магічне перетворення. Якщо кінцевий результат повинен бути цілим числом, повинні бути інструкції, які керують процесору, щоб здійснити перетворення перед зберіганням. Не знаючи foo()дострокового типу повернення , компілятор не мав би уявлення про необхідність коду перетворення.

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


Дякую за вашу відповідь (+1). Я не знаю деталей внутрішньої роботи компілятора, асемблера, процесора тощо. Основна ідея мого питання полягає в тому, що якщо я скажу (записую) повернене значення функції у вихідному коді, нарешті, після використання цієї функції, тоді мова дозволяє комп’ютеру знайти це значення без помилок. Тепер чому комп'ютер не може знайти тип подібним чином. Чому не вдалося знайти тип Data_i, оскільки Func_i()було знайдено значення повернення.
користувач106313

Я досі не задоволений. double foo(); int x; x = foo();просто видає помилку. Я знаю, що ми не можемо цього зробити. Моє запитання полягає в тому, що у виклику функції процесор знаходить лише повернене значення; чому він також не може знайти тип повернення?
user106313

1
@ user31782: Це не повинно. Існує прототип для foo(), тому компілятор знає, що з цим робити.
Blrfl

2
@ user31782: Процесори не мають поняття типу повернення.
Blrfl

1
@ user31782 Щодо питання про час компіляції: Можна написати мову, якою можна виконати весь аналіз цього типу під час компіляції. С - це не така мова. Компілятор C не може цього зробити, оскільки він не призначений для цього. Чи може це було по-іншому? Звичайно, але для цього знадобиться значно більше процесорної потужності та пам'яті. Підсумок - це не так. Він був розроблений таким чином, щоб комп'ютери дня найкраще справлялися.
Містер Міндор
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.