Жанр: Учеба
Язык С++ с комментариями
.... 687
Как уже было сказано, главная сила языка С — в его способности считывать
символы и решать, что с ними ничего не надо делать — причем выполнять это
быстро. Это действительно важное достоинство, которое нельзя недооценивать, и
цель С++ — не утратить его.
Вынужден вас огорчить — потоки С++ не обладают данным важным свойством, они
всегда работают медленнее С, а в некоторых реализациях — медленно до смешного
(правда, объективности ради стоит отметить, что мне попадались и совершенно
отвратительно реализованные FILE* потоки С, в результате чего код С++ работал
быстрее, но это просто недоразумение, если не сказать крепче!). Рассмотрим
следующую программу:
#include "stdio.h"
#include "time.h"
#include "io.h" // для open()
#include "fcntl.h"
#include "iostream.h"
#include "fstream.h"
void workc(char*);
void workcpp(char*);
void work3(char*);
int main(int argc, char **argv)
{
if (argc==3)
switch (*argv[2]-'0') {
case 1: {
workc(argv[1]);
break;
}
case 2: {
workcpp(argv[1]);
break;
}
case 3: {
work3(argv[1]);
break;
}
}
}
void workc(char* fn)
{
FILE* fil=fopen(fn,"rb");
if (!fil) return;
time_t t1; time(&t1);
long count=0;
while (getc(fil)!=EOF)
count++;
time_t t2; time(&t2);
fclose(fil);
cout""count""" bytes per """t2-t1""" sec.\n" ;
}
void workcpp(char* fn)
{
// для старых реализаций: ifstream fil(fn,ios::in|ios::binary);
ifstream fil(fn,ios_base::in|ios_base::binary);
if (!fil) return;
time_t t1; time(&t1);
long count=0;
while (fil.get()!=EOF)
count++;
time_t t2; time(&t2);
cout""count""" bytes per """t2-t1""" sec.\n" ;
}
class File {
int fd; // дескриптор файла
unsigned char buf[BUFSIZ]; // буфер стандартного размера
unsigned char* gptr; // следующий читаемый символ
unsigned char* gend; // конец данных
int uflow();
public:
File(char* fn) : gptr(0), gend(0) { fd=open(fn,O_RDONLY|O_BINARY); }
~File() { if (Ok()) close(fd); }
int Ok() { return fd!=-1; }
int gchar() { return (gptr"gend) ? *gptr++ : uflow(); }
};
int File::uflow()
{
if (!Ok()) return EOF;
int rd=read(fd,buf,BUFSIZ);
if (rd"=0) { // ошибка или EOF
close(fd);
fd=-1;
return EOF;
}
gptr=buf;
gend=buf+rd;
return *gptr++;
}
void work3(char* fn)
{
File fil(fn);
if (!fil.Ok()) return;
time_t t1; time(&t1);
long count=0;
while (fil.gchar()!=EOF)
count++;
time_t t2; time(&t2);
cout""count""" bytes per """t2-t1""" sec.\n" ;
}
Ее нужно запускать с двумя параметрами. Первый параметр — это имя (большого)
файла для чтения, а второй — цифра 1, 2 или 3, выбирающая функцию workc(),
workcpp() или work3() соответственно. Только не забудьте про дисковый кэш, т.е.
для получения объективных результатов программу нужно запустить несколько раз
для каждого из вариантов.
Необычным местом здесь является функция work3(). Она написана специально для
проверки "честности" реализации стандартных средств ввода-вывода С — FILE*.
Если вдруг окажется, что workc() работает существенно медленнее work3(), то вы
имеете полное право назвать создателей такой библиотеки, как минимум, полными
неучами.
А сейчас попробуем получить информацию к размышлению — проведем серию
контрольных запусков и посмотрим на результат. И что же нам говорят безжалостные
цифры? На отлично оптимизированных реализациях мы получим разницу "всего" в 40%
(например, 8 сек. против 11), а вот на "обычных"... Для одного широко
распространенного коммерческого пакета (не будем показывать пальцем) я даже
получил отличие в 11 раз! (7 сек. против 79).
Почему же все так плохо? Все становится очевидным, стоит только взглянуть на
определения вызываемых функций.
Для С с его getc() в типичной реализации мы имеем:
#define getc(f) \
((--((f)-"level) "= 0) ? (unsigned char)(*(f)-"curp++) : \
_fgetc (f))
Т.е. коротенький макрос вместо функции. Как говорится — всего-ничего. А вот для
С++ все гораздо сложнее. Вот код из оптимизированной реализации-чемпиона:
inline
int get() { if (!ipfx1()) return EOF;
else { int ch = _strbuf-"sbumpc();
if (ch == EOF) set(ios::eofbit);
return ch;
} }
Проследим вызовы функций при обычном поведении:
inline
int ipfx1() { // Optimized version of ipfx(1).
if (!good()) { set(ios::failbit); return 0; }
else {
_IO_flockfile(_strbuf);
if (_tie && rdbuf()-"in_avail() == 0) _tie-"flush();
return 1;
}
}
// _IO_getc() — это похожая на getc() работа с буфером
inline int sbumpc() { return _IO_getc(this); }
Даже при беглом взгляде становится ясно, что количество выполняемого кода весьма
сильно превосходит то, что мы имеем в С. Но самое интересное заключается в том,
что количество "лишнего" кода существенно превышает показанную разницу в 40%. В
чем же дело? А дело в том, что узким местом данного цикла является обращение к
диску, и именно из-за стоимости дисковых операций получаются эти неожиданные
40%. Более подробно данная тема будет рассмотрена в посвященном оптимизации кода
разделе.
А что будет, если мы выделим большой буфер и зададим его нашим функциям?
void workc(char* fn)
{
FILE* fil=fopen(fn,"rb");
if (!fil) return;
if (Bsize"0 && setvbuf(fil,Buf,_IOFBF,Bsize)) return;
// . . .
}
void workcpp(char* fn)
{
// для старых реализаций: ifstream fil(fn,ios::in|ios::binary);
ifstream fil(fn,ios_base::in|ios_base::binary);
if (!fil) return;
// для старых реализаций: fil.rdbuf()-"setbuf(Buf,Bsize);
if (Bsize"0) fil.rdbuf()-"pubsetbuf(Buf,Bsize);
// . . .
}
Как ни странно, по сути ничего не изменится! Дело в том, что современные ОС при
работе с диском используют очень качественные алгоритмы кэширования, так что еще
один уровень буферизации внутри приложения оказывается излишним (в том смысле,
что используемые по умолчанию буферы потоков оказываются вполне адекватными).
Кстати, одним из хороших примеров необходимости использования многопоточных
программ является возможность ускорения работы программ копирования файлов,
когда исходный файл и копия расположены на разных устройствах. В этом случае
программа запускает несколько потоков, осуществляющих асинхронные чтение и
запись. Но в современных ОС в этом нет никакого смысла, т.к. предоставляемое
системой кэширование кроме всего прочего обеспечивает и прозрачное для
прикладных программ асинхронное чтение и запись.
Подводя итог, хочется отметить, что если ввод-вывод является узким местом вашего
приложения, то следует воздержаться от использования стандартных потоков С++ и
использовать проверенные десятилетиями методы.
21.4.6.3. Манипуляторы, определяемые пользователем, стр. 701
Коль скоро с эффективностью потоков ввода-вывода мы уже разобрались, следует
поговорить об удобстве. К сожалению, для сколько-нибудь сложного форматирования
предоставляемые потоками средства не предназначены. Не в том смысле, что средств
нет, а в том, что данные средства совершенно неудобны и легко выводят из себя
привыкшего к элегантному формату ...printf() программиста. Не верите? Давайте
попробуем вывести обыкновенную дату в формате dd.mm.yyyy:
int day= 31,
mon= 1,
year=1974;
printf("%02d.%02d.%d\n",day,mon,year); // 31.01.1974
cout""setfill('0')""setw(2)""day""'.'""setw(2)""mon""setfill(' ')""'.'
""year"""\n"; // тоже 31.01.1974
Думаю, что комментарии излишни.
За что же не любят потоки С и чем потоки С++ могут быть удобнее? У потоков С++
есть только одно существенное достоинство — типобезопасность. Т.к. потоки С++
все же нужно использовать, я написал специальный манипулятор, который, оставаясь
типобезопасным, позволяет использовать формат ...printf(). Он не вызывает
существенных накладных расходов и с его помощью приведенный выше пример будет
выглядеть следующим образом:
cout""c_form(day,"02")""'.'""c_form(mon,"02")""'.'""year""'\n';
Вот исходный код:
#include "iostream"
#include "ctype.h"
class c_formBase { // класс базовых операций
protected:
// для компактной записи
typedef std::ios_base::fmtflags fmt_t;
typedef std::basic_ostream"char,std::char_traits"char" " ostr_t;
typedef std::ios_base ios;
private:
const char* form; // формат
int arg1, arg2; // аргументы ширины/точности
fmt_t flags; // сохраняемые флаги
int width; // ширина
int prec; // точность
char fill; // символ-заполнитель
fmt_t myfl; // устанавливаемые флаги
protected:
// конструктор с разбором формата
// формат: [-|0] [число|*] [.[число|*]] [e|f|g|o|x]
c_formBase( const char* form_,
const int arg1_,
const int arg2_
)
: form(form_), arg1(arg1_), arg2(arg2_) {
const char* iptr=form; // текущий символ строки формата
myfl=0;
fill=0;
if (*iptr=='-') { // выравнивание влево
myfl|=ios::left;
iptr++;
}
else if (*iptr=='0') { // добавляем '0'ли только если !left
fill='0';
iptr++;
}
width=0;
if (*iptr=='*') { // читаем ширину, если есть
width=arg1;
iptr++;
arg1=arg2; // сдвигаем агрументы влево
}
else if (isdigit(*iptr)) width=getval(iptr);
prec=0;
if (*iptr=='.') { // есть точность
if (*++iptr=='*') {
prec=arg1;
iptr++;
}
else if (isdigit(*iptr)) prec=getval(iptr);
else throw std::invalid_argument("c_form");
}
switch (*iptr++) {
case 0: return; // конец строки формата
case 'e': myfl|=ios::scientific; break;
case 'f': myfl|=ios::fixed; break;
case 'g': break;
case 'o': myfl|=ios::oct; break;
case 'x': myfl|=ios::hex; break;
default: throw std::invalid_argument("c_form");
}
if (*iptr) throw std::invalid_argument("c_form");
}
static int getval(const char*& iptr) { // чтение числа
int ret=0;
do ret=ret*10+ *iptr -'0';
while (isdigit(*++iptr));
return ret;
}
void prologue(ostr_t& os) { // настройка
flags=os.flags();
// очищаем floatfield и устанавливаем свои флаги
os.flags((flags & ~ios::floatfield) | myfl);
if (width) os.width(width);
if (fill) fill=os.fill(fill);
if (prec) prec=os.precision(prec);
}
void epilogue(ostr_t& os) { // восстановление
os.flags(flags);
if (fill) os.fill(fill);
if (prec) os.precision(prec);
}
};
template "class T"
class c_formClass : private c_formBase { // класс-шаблон для вывода типа T
T val; // выводимое значение
public:
c_formClass( T val_,
const char* form,
const int arg1,
const int arg2
)
: c_formBase(form,arg1,arg2), val(val_) { }
// оператор вывода
friend ostr_t& operator""(ostr_t& os, const c_formClass& cf_) {
c_formClass& cf=const_cast"c_formClass&"(cf_);
cf.prologue(os);
os""cf.val;
cf.epilogue(os);
return os;
}
};
template "class T" // функция-помощник
inline c_formClass"T" c_form( T val, // выводимое значение
const char* form, // формат вывода
const int arg1=0, // необязательные
const int arg2=0 // параметры
)
{ return c_formClass"T"(val,form,arg1,arg2); }
Принцип его работы основан на следующей идее: функция c_form""() возвращает
объект класса c_formClass"", для которого определена операция вывода в ostream.
Для удобства использования, c_form""() является функцией, т.к. если бы мы сразу
использовали конструктор некоторого класса-шаблона c_form"", то нам пришлось бы
явно задавать его параметры:
cout""c_form"int"(day,"02");
что, мягко говоря, неудобно. Далее, мы, в принципе, могли бы не использовать
базовый класс c_formBase, но это привело бы к совершенно ненужной повторной
генерации общего для всех вариантов шаблона кода.
И еще. Несмотря на то, что конструктор c_formBase возбуждает исключения
(std::invalid_argument), я не стал специфицировать это явным образом. Дело в
том, что большинство компиляторов не умеет встраивать функции с явно заданной
спецификацией возбуждаемых исключений, а одной из целей создания нашего
манипулятора являлась производительнотсь.
Для старых реализаций приходится использовать другой код, что, кстати сказать,
позволяет нам реально ощутить некоторые удобные изменения, привнесенные в С++
стандартом:
#include "iostream.h"
#include "ctype.h"
class c_formBase { // класс базовых операций
private:
const char* form; // формат
int arg1, arg2; // аргументы ширины/точности
long flags; // сохраняемые флаги
int width; // ширина
int prec; // точность
char fill; // символ-заполнитель
long myfl; // устанавливаемые флаги
protected:
// конструктор с разбором формата
// формат: [-|0] [число|*] [.[число|*]] [e|f|g|o|x]
c_formBase( const char* form_,
const int arg1_,
const int arg2_
)
: form(form_), arg1(arg1_), arg2(arg2_) {
const char* iptr=form; // текущий символ строки формата
myfl=0;
fill=0;
if (*iptr=='-') { // выравнивание влево
myfl|=ios::left;
iptr++;
}
else if (*iptr=='0') { // добавляем '0'ли только если !left
fill='0';
iptr++;
}
width=0;
if (*iptr=='*') { // читаем ширину, если есть
width=arg1;
iptr++;
arg1=arg2; // сдвигаем агрументы влево
}
else if (isdigit(*iptr)) width=getval(iptr);
prec=0;
if (*iptr=='.') { // есть точность
if (*++iptr=='*') {
prec=arg1;
iptr++;
}
else if (isdigit(*iptr)) prec=getval(iptr);
}
switch (*iptr++) {
case 0: return; // конец строки формата
case 'e': myfl|=ios::scientific; break;
case 'f': myfl|=ios::fixed; break;
case 'g': break;
case 'o': myfl|=ios::oct; break;
case 'x': myfl|=ios::hex; break;
}
}
static int getval(const char*& iptr) { // чтение числа
int ret=0;
do ret=ret*10+ *iptr -'0';
while (isdigit(*++iptr));
return ret;
}
void prologue(ostream& os) { // настройка
flags=os.flags();
// очищаем floatfield и устанавливаем свои флаги
os.flags((flags & ~ios::floatfield) | myfl);
if (width) os.width(width);
if (fill) fill=os.fill(fill);
if (prec) prec=os.precision(prec);
}
void epilogue(ostream& os) { // восстановление
os.flags(flags);
if (fill) os.fill(fill);
if (prec) os.precision(prec);
}
};
template "class T"
class c_formClass : private c_formBase { // класс-шаблон для вывода типа T
T val; // выводимое значение
public:
c_formClass( T val_,
const char* form,
const int arg1,
const int arg2
)
: c_formBase(form,arg1,arg2), val(val_) { }
// оператор вывода
friend ostream& operator""(ostream& os, const c_formClass"T"& cf_) {
c_formClass"T"& cf=(c_formClass"T"&) cf_;
cf.prologue(os);
os""cf.val;
cf.epilogue(os);
return os;
}
};
template "class T" // функция-помощник (два аргумента)
inline c_formClass"T" c_form(T val, char* form, int arg1, int arg2)
{ return c_formClass"T"(val,form,arg1,arg2); }
template "class T" // функция-помощник (один аргумент)
inline c_formClass"T" c_form(T val, char* form, int arg1)
{ return c_formClass"T"(val,form,arg1,0); }
template "class T" // функция-помощник (без аргументов)
inline c_formClass"T" c_form(T val, char* form)
{ return c_formClass"T"(val,form,0,0); }
21.6.2. Потоки ввода и буфера, стр. 711
Функция readsome() является операцией нижнего уровня, которая позволяет...
Т.к. приведенное в книге описание readsome() туманно, вот часть стандарта,
относящаяся к ней:
streamsize readsome(char_type* s, streamsize n);
Эффект: Если !good() вызвать setstate(failbit), которая может возбудить
исключение. Иначе извлечь символы и поместить их в массив, на который указывает
s. Если rdbuf()-"in_avail() == -1, вызвать setstate(eofbit) (которая может
возбудить исключение ios_base::failure (lib.iostate.flags)) и не извлекать
символы;
Если rdbuf()-"in_avail() == 0, не извлекать символы
Если rdbuf()-"in_avail() " 0, извлечь min(rdbuf()-"in_avail(),n)).
Возвращает: Количество прочитанных символов.
23.4.3.1. Этап 1: выявление классов, стр. 773
Например, в математике окружность — это частный случай эллипса, но в
большинстве программ окружность не нужно выводить из эллипса, или делать эллипс
потомком окружности.
Думаю, что стоит по-подробнее рассмотреть данный конкретный случай, т.к. он
иллюстрирует довольно распространенную ошибку проектирования. На первый взгляд
может показаться, что идея сделать класс Circle производным от класса Ellipse
является вполне приемлемой, ведь они связаны отношением is-a: каждая окружность
является эллипсом. Некорректность данной идеи станет очевидной, как только мы
приступим к написанию кода.
У эллипса, кроме прочих атрибутов, есть два параметра: полуоси a и b. И
производная окружность их унаследует. Более того, нам нужен один единственный
радиус для окружности и мы не можем для этих целей использовать один из
унаследованных атрибутов, т.к. это изменит его смысл и полученный от эллипса код
перестанет работать. Следовательно мы вынуждены добавить новый атрибут — радиус
и поддерживать в корректном состоянии унаследованные атрибуты. Очевидно, что
подобного рода наследование лишено смысла, т.к. не упрощает, а усложняет
разработку.
В чем же дело? А дело в том, что понятие окружность в математическом смысле
является ограничением понятия эллипс, т.е. его частным случаем. А наследование
будет полезно, если конструируемый нами объект содержит подобъект базового
класса и все унаследованные операции имеют смысл (рассмотрите, например,
операцию изменения значения полуоси b — она ничего не знает об инварианте
окружности и легко его разрушит). Другими словами, объект производного класса
должен быть расширением объекта базового класса, но не его частным случаем
(изменением), т.к. мы не можем повлиять на поведение базового класса, если он
нам не предоставил соответутвующих возможностей, например в виде подходящего
набора виртуальных функций.
А.5. Выражения, стр. 879
То есть "если нечто можно понять как объявление, это и есть объявление".
Т.к. сложные объявления С++ могут быть непонятны даже неновичку, стоит
прокомментировать приведенные в книге объявления. Неочевидность всех приведенных
объявлений основана на добавлении лишних скобок:
T(*e)(int(3)); эквивалентно T* e(int(3)); То, что инициализация указателя с
помощью int запрещена, синтаксичестим анализатором не принимается во внимание:
будет распознано объявление указателя и выдана ошибка.
T(f)[4]; эквивалентно T f[4];
T(a); эквивалентно T a;
T(a)=m; эквивалентно T a=m;
T(*b)(); объявление указателя на функцию.
T(x),y,z=7; эквивалентно T x,y,z=7;
B.13.2. Друзья, стр. 931
Приведенный в конце страницы пример нужно заменить на:
template"class C" class Basic_ops { // базовые операции с контейнерами
friend bool operator==""(const C&, const C&); // сравнение элементов
friend bool operator!=""(const C&, const C&);
// ...
};
Уголки ("") после имен функций означают, что друзьями являются функции-шаблоны
(поздние изменения стандарта).
Этот текст взят из списка авторских исправлений к 10 тиражу.
Почему в данном случае необходимы ""? Потому что иначе мы объявляем другом
operator==() не шаблон, т.к. до объявления класса в окружающем контексте не было
объявления operator==()-шаблона. Вот формулировка стандарта:
14.5.3. Друзья [temp.friend]
-1- Другом класса или класса-шаблона может быть функция-шаблон, класс-шаблон, их
специализации или обычная (не шаблон) функция или класс. Для объявления
функций-друзей которые не являются объявлениями шаблонов:
если имя друга является квалифицированным или неквалифицированным
идентификатором шаблона, то объявление друга ссылается на специализацию
функции-шаблона, иначе,
если имя друга является квалифицированным и в специфицированном классе или
пространстве имен найдена соответствующая функция не шаблон, то объявление
друга ссылается на эту функцию, иначе,
если имя друга является квалифицированным и в специфицированном классе или
пространстве имен найдена соответствующая функция-шаблон, то объявление друга
ссылается на эту функцию, иначе,
имя должно быть неквалифицированным и объявлять (или переобъявлять) обычную
(не шаблон) функцию.
Например:
template"class T" class task;
template"class T" task"T"* preempt(task"T"*);
template"class T" class task {
// ...
friend void next_time();
friend void process(task"T"*);
friend task"T"* preempt"T"(task"T"*);
template"class C" friend int func(C);
friend class task"int";
template"class P" friend class frd;
// ...
};
здесь функция next_time является другом каждой специализации класса-шаблона
task; т.к. process не имеет явных template-arguments, каждая специализация task
имеет функцию-друга process соответствующего типа и этот друг не является
специализацией функции-шаблона; т.к. друг preempt имеет явный template-argument
"T", каждая специализация класса-шаблона task имеет другом соответствующую
специализацию функции-шаблона preempt; и, наконец, каждая специализация
класса-шаблона task имеет другом все специализации функции-шаблона func.
Аналогично, каждая специализация класса-шаблона task имеет другом
класс-специализацию task"int", и все специализации класса-шаблона frd.
Оптимизация
Поговорим об оптимизации. Что нужно оптимизировать? Когда? И нужно ли вообще? В
этих вопросах очень легко заблудиться, если сразу же не выбрать правильную точку
зрения. Как только мы посмотрим на наш код с точки зрения пользователя, сразу же
все становится на свои места:
Программа должна делать то, что от нее требуется.
Она должна делать это хорошо.
Именно так — глупо оптимизировать неправильно работающий код. Если же
пользователя устраивает текущее быстродействие, не стоит искать неприятности.
Итак, анализ проведен, решение принято — ускоряемся. Что может ускорить нашу
программу? Да все, что угодно, вопрос поставлен некорректно. Что может
существенно ускорить нашу программу? А вот над этим уже стоит подумать.
Прежде всего, стоит подумать о "внешнем" ускорении, т.е. о не приводящих к
изменению исходного кода действиях. Самый широкораспространенный метод --
использование более мощного аппаратного обеспечения. Увы, иногда это не самый
эффективный способ. Зачастую гораздо большего можно добиться путем правильного
конфигурирования того, что есть. Например, работа с БД — практически всегда
самое узкое место. Должно быть очевидно, что правильная настройка сервера БД --
одно из самых важных действий и за него всегда должен отвечать компетентный
специалист. Вы будете смеяться, но грубые оплошности сисадминов происходят
слишком часто, чтобы на них не обращать внимание (из моей практики: неоднократно
время работы приложения уменьшалось с нескольких часов до нескольких минут (!)
из-за очевидной команды UPDATE STATISTICS, фактически, перед анализом плана
испонения тяжелых SQL-запросов всегда полезно невзначай поинтересоваться
актуальностью статистики. Не менее редким происшествием является "случайная
потеря" индекса важной таблицы в результате реорганизации или резервного
копирования БД).
Коль скоро среда исполнения правильно сконфигурирована, стоит обратить внимание
непосредственно на код. Очевидно, что максимальная скорость эскадры определяется
скоростью самого медленного корабля. Он-то нам и нужен. Если "эскадрой" является
набор SQL-запросов работающего с БД приложения, то, как правило, никаких
трудностей с определением узких мест не возникает. Трудности возникают с
определением узких мест "обычных" приложений.
Узкие места нужно искать только с помощью объективных измерений, интуиция в
данной области часто не работает (не стоит утверждать, что не работает вообще).
Причем измерять относительную производительность имеет смысл только при
"релиз"-настройках компилятора (при отключенной оптимизации узкие места могут
быть найдены там, где их нет. Увы, данного рода ошибки допускают даже опытные
программисты). Действительно серьезным подспорьем здесь являются профайлеры --
неотъемлемая часть любой профессиональной среды разработки.
Когда критический участок кода локализован, можно приступать к непосредственному
анализу. С чего начать? Начинать нужно с самых ресурсоемких операций. Как
правило, по требуемому для исполнения времени, операции легко разделяются на
слои, отличающиеся друг от друга на несколько порядков:
работа с внешними устройствами
системные вызовы
вызовы собственных функций
локальные управляющие структуры
Например, не стоит заниматься вопросами размещения управляющей переменной цикла
в соответствующем регистре процессора, если в данном цикле происходит обращение
к диску. Вызовы собственных функций существенно отличаются от системных вызовов
тем, что когда мы обращаемся к системе, происходит переключение контекста потока
(системный код имеет больше привелегий, обращаться к нему можно только через
специальные шлюзы) и обязательная проверка достоверности переданных аргументов
(например, система проверяет действительно ли ей передана корректная строка
путем ее посимвольного сканирования, если при этом произойдет нарушение прав
доступа или ошибка адресации, то приложение будет об этом проинформировано; тем
самым исключается возможность сбоя внутри ядра системы, когда неясно что делать
и кто виноват, наиболее вероятный результат этого — blue death screen, system
trap и т.д., т.е. невосстановимый сбой самой системы).
Как правило, только в исключительных случаях заметного ускорения работы можно
достичь путем локальных улучшений (которыми пестрят древние наставления: a+a
вместо 2*a, for (register int i; и т.д.), современные компиляторы прекрасно
справля
Закладка в соц.сетях