Упражнение 5-11. Инициализирайте
_new_handler да сочи към freeStoreExeption и изпълнете отново програмата
exhaustFreeStore(), дефинирана в раздел 4.1 на тази
глава.
5.5. Свързване, безопасно относно
типовете
Презаредимостта дава възможност за многократни появи на един
и същ не статичен идентификатор на функция. Това е едно лексикално удобство,
което се поддържа на нивото на текста на програмата. По- нататъшните компоненти
на системата за компилация, обаче, изисква всеки не статичен идентификатор на
функция да бъде именуван уникално. Например, повечето свързващи редактори
свързват външните псевдоними (references) лексикално. Ако свързващият редактор
срещне два или повече представителя на идентификатора print, той не може да
анализира типовете на сигнатурите за да направи разлика между представителите (
в този момент на компилацията тази информация обикновено е загубена ). По-скоро
свързващият редактор отбелязва print като дефинирана многократно и приключва
работата си.
За да бъде решен този проблем всеки идентификатор на функция
се кодира с уникално вътрешно име. Така по-нататъшните компоненти на системата
за компилация работят само с тези кодирани имена. Подробностите, свързани с
трансформацията на имената не са особено важни; вероятно те са различни за
различните реализации. Общият алгоритъм кодира информацията за сигнатурата и я
добавя към името на функцията. В два специални случая кодирането ще предизвика
грешка по време на свързване и ще причини неуспешно приключване на
компилацията:
1. Несъвместими декларации на функция в отделни
файлове.
2. Обръщения от функции от други езици, съществени за С. Този раздел
разглежда тези два специални случая.
Вътрешно файлови
декларации
В първия случай дадена функция непреднамерено е
декларирана различно в два отделни файла; двете декларации трябва да представят
една и съща функция. Например, във файла token.C функцията addToken() е
дефинирана да получава един аргумент от тип unsigned char. Във файла lex.C,
където тази функция се вика, addToken() е декларирана да получава един аргумент
от тип char.
// in file token.C
addToken( unsigned char tok
)
{ /* ... */ } // in file lex.C
extern addToken( char
);
Едно обръщение към addToken() в lex.C ще предизвика грешка във фазата
на свързващото редактиране. Аргументи от тип unsigned char и char се кодират
различно. Функцията addToken(), декларирана в lex.C, ще бъде отбелязана като не
дефинирана функция. Ако програмата бъде компилирана успешно може да се случи
следното:
Компилираната програма се тества на AT&T 3B20. Тя се
изпълнява правилно и се изпраща на ново място, където се използват VAX 8550.
Компилира се без какъвто и да е проблем. За нещастие, още при първото изпълнение
тя приключва неуспешно. Даже най-простият тест на програмата не работи. Какво се
случва? Ето част от зададените от Token декларации:
enum Tokens {
//
...
INLINE = 128;
VIRTUAL = 129; // ... };
Обръщението към
addToken() изглежда така: curTok = INLINE; addToken( curTok ); Стойностите от
тип char са реализирани като тип със знак върху 8550. Върху 3B20 те са
реализирани като unsigned. Неправилната декларация на addToken() не е показана
на 3B20; на 8550, обаче, всеки знак стойност по-голяма от 127 предизвиква
препълване.
Понеже във всеки момент компилаторът обработва по един файл
той не може обикновено да открие нарушенията на типовете в различните файлове.
Както вече видяхме, това нарушаване на типовете може да стане източник на
сериозни програмни грешки. Вътрешното кодиране на имената на функциите и тяхната
сигнатура предоставя някаква мярка за вътрешно файлова проверка на обръщенията
към функции. Такива грешки и други като тях се откриват по време на
свързването.
Погрешните декларации на външни променливи между различни
файлове, обаче, не могат да бъдат открити по време на компилация. Грешки от типа
на описаната по-долу могат да бъдат открити само по време на изпълнение ако се
получи някакво изключение или неправилен изход на програмата. // in token.C
unsigned char lastTok = 0; // in lex.C extern char lastTok; // one token
history
Няколко думи за заглавните
файлове
Описаното използване на заглавните файлове е фундаментално за
предпазване от такъв тип вътрешни грешки. Всеки заглавен файл предлага едно
централизирано разполагане на декларациите на всички extern променливи,
прототипи на функции, дефиниции на класове и inline функции. Файлове, които
трябва да декларират променлива, функция или клас включват заглавен/ни/
файл/ове/.
Това осигурява две предохранителни мерки. Първо, гарантира се,
че всички файлове съдържат една и съща декларация. Второ, когато декларацията на
дадена променлива трябва да бъде променена е необходимо да се направи промяна
само в заглавния файл. Така се игнорира възможността за неправилно коригиране на
декларацията в някой файл. Нашият пример с addToken() ще предостави заглавния
файл
token.h. // in token.h
enum Tokens { /* ... */ };
extern
unsigned char lastTok;
extern addToken( unsigned char ); // in
lex.C
#include "token.h" // in token.C
#include
"token.h"
Трябва обаче да бъде отделено известно внимание на
проектирането на заглавните файлове. Предлаганите декларации трябва логично да
се допълват. Всеки заглавен файл отнема време при компилация. Ако е прекалено
дълъг или с много напълно различни елементи, програмистите с неохота ще приемат
загубата на време при компилация, включвайки ги.
Едно второ съображение
е, че даден заглавен файл не трябва никога да съдържа не статични дефиниции. Ако
два файла в една и съща програма включват заглавен файл с външни дефиниции
повечето свързващи редактори ще отстранят програмата поради многократно
дефинирани идентификатори. Понеже стойности от тип const често се включват в
заглавните файлове свързването по подразбиране на идентификатори const е
статично. Следователно константите могат да бъдат дефинирани само вън от
заглавите файлове.
Обръщения към функции от други
езици
Ако програмистът иска да се обърне към функция, написана на
друг език за програмиране - най-вероятно С - е необходим заобикалящ механизъм,
който да предотврати кодирането на името на функцията. Този механизъм, наречен
свързваща директива, има едноредова съставна синтактична форма:
extern
"C" void exit(int);
extern "C" {
printf( const char* ... );
scanf(
const char* ... ); }
extern "C" { #include <string.h>
}
Свързващата директива се състои от ключовата дума extern, следвана от
низов литерал и един “обичаен” прототип на функция. Въпреки, че функцията е
написана на друг език, обръщението към нея също се проверява за типово
съответствие. Във фигурните скоби могат да бъдат записани много функции. Те
/скобите/ служат като ограничител и не въвеждат ново ниво на
обхват.
Свързващата директива може да бъде зададена само с файлов обхват.
Следните кодови фрагменти са некоректни и ще бъдат отбелязани като грешки по
време на компилация:
char *copy( char *src, char *dst )
{ // error:
linkage directive must be at file scope
extern "C" strlen( const char*
);
if ( !dst ) dst = new char [ strlen(scr)+1 ];
//...
return dst;
}
Ако преместите свързващата директива до файлов обхват функцията ще се
компилира:
extern "C" strlen( const char* );
char *copy( char *src,
char *dst ) {
if ( !dst ) dst = new char [ strlen(scr)+1
];
//...
return dst; }
Най-подходящото място на свързващата
директива, обаче, е във заглавен файл.
Ако се предвижда С++ функции да бъдат
викани от други езици, те също трябва да бъдат заобиколени.
Свързваща
директива може да бъде определена само за един представител на презаредима
функция. Програма, която включва следните два заглавни файла е
неправилна:
extern "C" strlen( const char* ); // in String.h
extern
"C" strlen( const String& ); // in String.h
Презареждането на sqrt(),
което следва, илюстрира едно типично използване на свързваща
директива:
class Complex;
class BigNum;
extern Complex& sqrt(
Complex& );
extern "C" double sqrt( double );
extern BigNum&
sqrt(BigNum& );
Представителят sqrt() на С е заобиколен; допълненият
представител на С++ класа не е.
Упражнение 5-12. exit(), printf(),
malloc(), strcpy() и strlen() са библиотечни функции на езика С. Изменете
следната програма на С така, че да може да се компилира и свързва в С++. char
*str ="hello";
main() { /* C language program */
char *s, *malloc(),
*strcpy();
s = malloc( strlen(str)+1 );
strcpy( s, str );
printf("%s,
worldn", s );
exit( 0 ); }
Упражнение 5-13. Раздел 3.10 дефинира
функцията binSearch(). Ние желаем да я направим достъпна за програми, написани
на С. Как трябва да я декларираме?
Упражнение 5-14. Библиотечните
тригонометрични функции на С са декларирани по следния начин: double sin( double
); double cos( double ); double tan( double );
Покажете как тези
декларации биха могли да бъдат презаредени за да възприемат аргументи от
класовия тип RealNum и Complex?