Справжня причина зводиться до принципової різниці у намірах між C та C ++ з одного боку, та Java та C # (лише для кількох прикладів) з іншого. З історичних причин велика частина дискусій тут стосується C, а не C ++, але (як ви, мабуть, уже знаєте), C ++ є досить прямим нащадком C, тому те, що він говорить про C, стосується C ++.
Хоча вони значною мірою забуті (а їх існування іноді навіть заперечується), перші перші версії UNIX були написані мовою асемблерів. Значна частина (якщо не виключно) оригінальної цілі C була портом UNIX з мови асемблера на мову вищого рівня. Частина наміру полягала в тому, щоб написати якомога більше операційної системи мовою вищого рівня - або дивитись на це з іншого напрямку, щоб мінімізувати кількість, яку потрібно було написати мовою асемблера.
Для цього C потрібно було забезпечити майже той самий рівень доступу до обладнання, що і мова монтажу. PDP-11 (для прикладу) відображає регістри вводу / виводу за конкретними адресами. Наприклад, ви прочитали одне місце пам'яті, щоб перевірити, чи була натиснута клавіша на системній консолі. Один біт був встановлений у тому місці, коли були дані, які очікують на читання. Потім ви прочитали байт з іншого вказаного місця, щоб отримати код ASCII натиснутої клавіші.
Так само, якби ви хотіли надрукувати деякі дані, ви перевірили б інше вказане місце, і коли пристрій виводу буде готовий, ви записуєте свої дані в інше вказане місце.
Для підтримки драйверів запису для таких пристроїв, C дозволив вам вказати довільне розташування за допомогою певного цілого типу, перетворити його на покажчик та прочитати чи записати це місце в пам'яті.
Звичайно, це має досить серйозну проблему: не кожна машина на землі має пам'ять, викладену ідентично PDP-11 з початку 1970-х. Отже, коли ви берете це ціле число, перетворюєте його на покажчик, а потім читаєте чи записуєте через цей вказівник, ніхто не може надати будь-якої розумної гарантії того, що ви збираєтеся отримати. Просто для наочного прикладу читання та запис може відображати окремі регістри в апараті, тож ви (всупереч нормальній пам'яті) якщо щось пишете, то спробуйте прочитати його назад, те, що ви прочитали, може не відповідати тому, що ви написали.
Я бачу кілька можливостей, які залишає:
- Визначте інтерфейс для всіх можливих апаратних засобів - вкажіть абсолютні адреси всіх місць, які ви хочете прочитати чи написати, щоб взаємодіяти з обладнанням будь-яким способом.
- Заборонити цей рівень доступу та постановити, що кожен, хто хоче робити такі речі, повинен використовувати мову мовлення.
- Дозвольте людям це робити, але залиште їх, щоб вони прочитали (наприклад) посібники для обладнання, на яке вони орієнтуються, і написати код, щоб він відповідав обладнанням, яке вони використовують.
З них 1 здається досить безглуздим, що навряд чи варто додаткового обговорення. 2 в основному викидає основний намір мови. Це залишає третій варіант по суті єдиним, який вони могли б розумно розглянути взагалі.
Ще один момент, який виникає досить часто - це розміри цілих чисел. C займає "положення", яке int
має бути природним розміром, запропонованим архітектурою. Отже, якщо я програмую 32-бітний VAX, int
мабуть, це буде 32 біти, але якщо я програмую 36-бітний Univac, int
мабуть, це буде 36 біт (і так далі). Мабуть, не розумно (а це навіть не можливо) писати операційну систему для 36-бітного комп'ютера, використовуючи лише типи, які гарантовано мають кратні розміри 8 біт. Можливо, я просто поверхневий, але мені здається, що якби я писав ОС для 36-бітної машини, я, мабуть, хотів би використовувати мову, яка підтримує 36-бітний тип.
З мовної точки зору, це призводить до ще більш невизначеної поведінки. Якщо я візьму найбільше значення, яке вміститься у 32 біти, що буде, коли я додаю 1? У типовому 32-бітному апаратному забезпеченні він перекинеться (або, можливо, викине якусь технічну несправність). З іншого боку, якщо він працює на 36-бітному апаратному забезпеченні, він просто ... додасть його. Якщо мова буде підтримувати операційну систему написання, ви не можете гарантувати жодної поведінки - ви просто повинні дозволити як розміри типів, так і поведінку переповнення змінюватися залежно від одного.
Java та C # можуть ігнорувати все це. Вони не призначені для підтримки операційних систем запису. З ними у вас є пара варіантів. Одне полягає в тому, щоб апаратне забезпечення підтримувало те, чого вони вимагають - оскільки вони вимагають типів 8, 16, 32 та 64 біт, просто будуйте апаратне забезпечення, яке підтримує ці розміри. Інша очевидна можливість полягає в тому, щоб мова працювала лише над іншим програмним забезпеченням, яке забезпечує навколишнє середовище, яке вони хочуть, незалежно від того, що може хотіти базове обладнання.
У більшості випадків це насправді не вибір або вибір. Швидше за все, багато реалізацій трохи роблять і те, і інше. Ви зазвичай запускаєте Java на JVM, що працює в операційній системі. Найчастіше ОС записується на С, а JVM - на C ++. Якщо JVM працює на процесорі ARM, дуже ймовірно, що процесор включає в себе розширення Jazelle ARM для більш детального адаптації обладнання до потреб Java, тому менше потрібно робити програмне забезпечення, а код Java працює швидше (або менше) повільно, все одно).
Підсумок
C і C ++ мають невизначену поведінку, оскільки ніхто не визначив прийнятну альтернативу, яка дозволяє їм робити те, що їм призначено. C # і Java застосовують інший підхід, але такий підхід погано (якщо він взагалі відповідає) цілям C і C ++. Зокрема, не здається, що це забезпечує розумний спосіб запису системного програмного забезпечення (наприклад, операційної системи) на більшість довільно вибраних апаратних засобів. Обидва зазвичай залежать від можливостей, які надає існуюче системне програмне забезпечення (як правило, написане на C або C ++) для виконання своїх завдань.