Б. Страуструп - Дизайн и Эволюция C++. 2006 (1160775), страница 12
Текст из файла (страница 12)
По воспоминаниям Стью Фельдмана, в самой первой реавизации С «пйЬ С!аяяея программист не мог напрямую обращаться к с)тгя. Как только мне указали на этот недостаток, я быстро его устранил. Без сьг я или какого-то эквивалентного механизма функции-члены нельзя было бы использовать для манипуляций со связанным списком. Указатель сЬгя — это вариация С++ на тему ссылки тнтЯ из Япш!а Иногда меня спрашивают, почему С)тгв — это указатель, а не ссылка и почему он называется СЬгв, а не яе1й.
Дело в том, что, когда с)тз.я был введен в С гу((Ь С1авяея, механизма ссылок в нем еше не было, а терминологию С++ я заимствовал из Яшц1а, а не из Втавгавы Если бы функция ясас)с.рияЬ() была объявлена с ключевым словом гп1гпе, то сгенерированный код выглядел бы так: воъо д(р) /* сгенерированный С-код */ яггысг вгаск* р; ( Гг ((р->гор)>(р->иах)) еггог("стек пуст") *(р- гор)++ = 'с'; ) Именно такой код написал бы программист, работающий на С. 2.6.
Статический контроль типов У меня не сохранилось ни воспоминаний о дискуссиях, ни заметок, касающихся внедрения статического контроля типов в С %(гЬ С!аваев. Синтаксис и семантика, впоследствии одобренные в стандарте АХБ1 С, просто были включены в готовом виде в первую реализацию С впгЬ С!эяяея. После этого в результате серии экспериментов появились более строгие правила, ныне вошедшие в С++.
Для меня статический контроль типов после опыта работы с А1яо!68 и Вппп1а был абсолютной необходимостью, вопрос заключался только в том, в каком виде его реализовать. Чтобы не возникло конфликта с С, я решил разрешить вызовы необъявленных функций н не проверять для них типы. Конечно, это была «дыра» в системе типов, впоследствии предпринимались попытки устранить основной источник ИИИИИИВВ Язык С )))(!Ф С(аззез ошибок и уже в С++ проблему решили путем запрета на вызовы необъявленных функций. Одно наблюдение обрекло на провал все попытки найти компромисс и тем самым обеспечить большую совместимость с С; программисты, изучавшие С и 1(Ь С!аззез, забывали, как искать ошибки времени исполнения, вызванные несоответствием типов.
Привыкнув полагаться на проверку и преобразования типов, которые обеспечивал С ъчгЬ С!аззез, пользователи уже не могли быстро отыскать глупейшие ляпы, просачивавшиеся в С-программы, где контроля типов не было. К тому же забывалось о мерах предосторожности против таких ошибок, которые опытные программисты на С принимают, не задумываясь. Ведь «в С цтТЬ С!аззез такого просто не бывает». Таким образом, число ошибок, вызванных необнаруженным несоответствием типов аргументов, становится меньше, но их серьезность и время, потраченное на их поиск, возрастают.
А в результате — недовольство программистов и требование ужесточить систему контроля типов. Самым интересным экспериментом с «неполным статическим контролем типов» была попытка разрешить вызовы необъявленных функций, но запоминать типы переданных аргументов, с тем чтобы при повторном вызове их можно было проверить. Когда Уолтер Брайт (Ъа!гег Вг)яЬг) много лет спустя независимо открыл этот способ, он назвал его автопрототипированием, вспомнив употребляемый в АХЯ С термин прототип, обозначающий объявление функции.
Опыт показал, что автопрототипирование позволяло отловить многие ошибки, и поначалу доверие программистов к системе контроля типов повысилось. Однако ошибки в функции, которая вызывалась только один раз, таким способом не обнаруживались, и из-за этого автопрототипирование окончательно подорвало веру в проверку типов и привело к еще худшей навязчивой идее, нежели все, с чем я встречался в С или ВСР1 В С пч(Ь С!аззез введена нотация й (чоЫ) для функции й, не принимающей аргументов, вместо нотации й () в С, обозначающей функцию с любым числом аргументов без проверки типов. Однако вскоре пользователи убедили меня, что нотация й (чойг)) неизящна, а объявление с помощью й () функции, которая может принимать аргументы, далеко от интуитивно очевидного.
Поэтому после некоторых экспериментов было решено оставить нотацию й ( ) для функции, не принимающей никаких аргументов, ибо именно этого ожидает неискушенный пользователь. Чтобы отважиться на такой разрыв с традициями С, мне потребовалась поддержка со стороны Дуга Макилроя и Денниса Ричи. Только после того как они охарактеризовали нотацию й (чоус)) словом «гадость», я решил придать й ( ) очевидную семантику. Однако и по сей день правила контроля типов в С гораздо слабее, чем в С++, а комитет по стандартизации АХБ1 С одобрил «отвратительную й (дои)», впервые появившуюся в С ич(Ь С!аззез.
2.6.1. Сужающие преобразования Еп(е одной ранней попыткой усилить контроль типов в С ««ИЬ С1аззез был запрет на неявные преобразования, приводящие к потере информации. Как и многие другие, я не раз попадался в ловушку, иллюстрируемую следующими примерами (конечно, в реальных программах найти такую ошибку будет потруднее): хо«а й() ( 1опд гпс 1пд = 55000; ЛИИИИИИ1 Статический контроль типов Я решил попробовать запретить все преобразования, не сохраняющие значение, то есть потребовать явного оператора преобразования в случаях, когда больший обьект копируется в меньший: чо1г( д(1опд 1пд, ьпс 1) /* эксперимент */ ( 1пс 11 = 1пд; 11 = (ьпк)1пд; сйах с = 1; с = (сйат)).; ) Эксперимент с треском провалился. Все С-программы, которые я просмотрел, содержали множество присваиваний значений типа 1пс переменным типа сйаг.
Разумеется, коль скоро программы работали, большинство таких присваиваний было безопасно: либо значение было маленьким и не обрезалось, либо отбрасывание старших разрядов предполагалось или, по крайней мере, в данном контексте считалось безвредным. Пользователи С ъчгЬ С)аззез вовсе не желали такого отхода от С. Я все еще пытаюсь найти какой-то способ разрешить эти проблемы (см.
раздел 14.3.5.2). 2.6.2. О пользе предупреждений Я рассматривал возможность проверки присваиваемых значений во время исполнения, но за зто пришлось бы заплатить серьезным уменьшением скорости и увеличением размера кода. К тому же, на мой взгляд, диагностировать ошибку было уже поздновато. Проверки преобразований во время исполнения — да и любые другие проверки на данной стадии — были отнесены к разряду «идей о будущей поддержке в отладочной средеь. Вместо этого я применил способ, позже ставший стандартным для обхождения с теми недостатками С, которые, на мой взгляд, были слишком серьезными, чтобы игнорировать их, и слишком укоренившимися в С, чтобы их устранять.
Я заставил препроцессор С )у1й С!аззез (а позже н свой компилятор С++) выдавать предупреждения: но1б 6(1опд 1пд, 1пс 1) ( 1пс 11 = 1пд; 11 = (ьпс)1пд; сйаг с = 1; 1пс 11 = 1пд; /* /* 1пс 12 = 257; сйат с = 12; /* /* ) 11 становится отрицательным (-536) на машинах с 16-разрядными целыми */ отбрасывание: с становится равным 1 */ на машинах с 8-разрядным айат */ /* ошибка: преобразование понижает тип */ /* отбрасывание для 16-разрядных целых */ /* ошибка: преобразование понижает тип */ /* отбрасывание */ // неявное преобразование: предупреждение // явное преобразование: нет предупреждения // слишком устойчивая идиома: нет предупреждения ИШИИИ>ИВ Язык С )д/!Ф С!аззез с1авв х ( // ) д(1пс 1, ьпс к, ьпс 1) // предупреждение: с1авв х определен как тип значения, // возвращаемого д() (вы не забыли ';' после ')' 7) // предупреждение: 1 не используется ( 12 (1 = 7) ( // предупреждение: присваивание константы в условии // // (ха077 == О) ( // предупреждение: выражение == как операнд // для а // Даже первая версия С!гоп( (см.
раздел З.З) выдавала такие предупреждения. Они появились в результате осознанного проектного решения, а не были добавлены потом. Много позже первое из зтих предупреждений превратилось в ошибку — был введен запрет на определение новых типов в типах возвращаемых значений и аргументов. 2.7. Почему С? На презентациях С юЫЬ С!аззез меня часто спрашивали: «Почему Вы использовали Су Почему не Рааса!?» Один из вариантов ответа можно найти в работе !Зггопзггпр, 1986с]: И тогда, и сейчас безусловные предупреждения выдавались для преобразований 1опц-»1пс и с(оиЬ1е-»йпс, поскольку я не вижу причин для признания их законными.
Это просто результат определенного недоразумения — арифметика с плавающей точкой появилась в С до введения явных преобразований. Такие предупреждения устраивали пользователей. Более того, меня самого и многих других они не раз выручали. С преобразованием же 1пс-»сваг я ничего не мог сделать. И по сей день такие преобразования компилятор АТ« Т С++ пропускает без предупреждений. Я решил выдавать безусловные предупреждения только в случаях, когда «вероятность ошибки больше 90%», причем был учтен опыт компилятора С и верификатора !зп(, которые чаще, чем хотелось бы, выдавали «ложные» предупреждения относительно того, что не мешало программе корректно работать.