Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C.doc
Скачиваний:
29
Добавлен:
08.05.2015
Размер:
1.17 Mб
Скачать

Порядок передачи данных в функцию. Модификатор pascal

Если вызывается функция, написанная на языке С, то параметры заносятся в стек справа налево, а следом за ними заносится адрес возврата. Кроме того, вызываемая функция не удаляет параметры из стека. Это делает вызывающая функция. Также имена всех объектов программы дополняются символом подчеркивания _.

Если вызывается функция, написанная на языке Паскаль или Фортран, то параметры заносятся в стек слева направо, а следом за ними заносится адрес возврата. Кроме того, вызываемая функция удаляет параметры из стека. Имена объектов не меняются. Поэтому просто так подключить на этапе компоновки модули, написанные на этих языках программирования, нельзя. Их необходимо объявить в программе, написанной на языке С, с модификатором pascal. Например:

void pascal fun1(int par1, int par2, int par3);

Кроме того, с помощью опций компилятора можно установить, что все функции будут передавать параметры, используя Паскаль-соглашения по вызову. Установить С-соглашение по вызову можно, используя модификатор cdecl. Например:

void cdecl fun2(int par1, int par2, int par3);

Передача данных в программу. Функция main

Можно передавать данные в программу, запуская полученный в результате компиляции и компоновки выполняемый exe-файл и передавая в качестве параметров требуемые данные. Turbo-C поддерживает для организации таких программ кроме пустого типа функции main с пустым набором параметров также следующие формы:

void main(int argn, char *arg[]);

int main(int argn, char *arg[]);

где argn - количество аргументов, arg - массив указателей на аргументы, причем arg[0] - адрес строки вызова данной программы. то есть минимальное значение argn - 1.

Во втором случае функция возвращает номер ошибки ДОС.

В качестве примера рассмотрим программу, печатающую количество переданных аргументов и сами аргументы:

#include <stdio.h>

void main(int n,char *arg[])

{

int i;

printf("\nЗапущена программа %s\n",arg[0]);

for (i=1; i<n; i++) printf("Параметр %d: %s\n",i,arg[i]);

}

Пусть файл готовой задачи имеет имя p_main.exe, а находится он в каталоге proba на диске C:. При запуске этой задачи мы указали строку

p_main abc g3j

В этом случае на печать выведется следующее сообщение:

Запущена программа C:\PROBA\P_MAIN.EXE

Параметр 1: abc

Параметр 2: g3j

Разбор типовых ошибок и недочетов при программировании

1. Ошибки при написании отношений.

Часто путают отношения и оператор присваивания. Рассмотрим конструкцию:

if (i=0) /* должно быть i==0 */

{

. . .

}

При этом выполнится оператор присваивания и i станет равным 0. Далее значение i проверится в качестве условия - это ложь, и группа операторов внутри оператора if не будет выполнена. Очевидно, это не то, что мы хотели получить.

2. Ошибки при отсутствии прототипов функций.

Если не указать прототипы функций, то компилятор с С считает, что все функции возвращают значения типа int, а параметры не преобразуются к нужному типу. Например, если указана в программе конструкция

sin(2.5)

но не подключен файл прототипов математических функций math.h (не описан прототип функции sin), то значение синуса будет вычислено верно, но принято неверно вызывающей функцией. Результат будет неверен (непредсказуемый), так как из 8 байт результата (тип double) будут взяты первые 2 байта и проинтерпретированы как данное типа int.

3. Ошибки при использовании указателей.

Рассмотрим фрагмент программы:

int *a;

*a=12;

При выполнении оператора присваивания число 12 будет занесено неизвестно по какому адресу, так как операция инициализации указателя (присвоение адреса) не была сделана. Как правило, вначале С этот адрес устанавливает нулевым, значит, мы запишем число 12 по нулевому адресу. Так как адреса с нулевого занимают вектора прерываний, то может быть разрушена операционная система, и дальнейшая работа вычислительной машины становится невозможной.

4. Неверное использование объектов вещественного типа.

Пример: пусть требуется выполнить цикл по параметру от 0 до 2 с шагом 0,1 (то есть 21 раз). Используем фрагмент программы:

double a;

. . .

for (a=0.; a<=2.; a+=0.1) {. . .}

Поскольку вычисления с плавающей точкой производятся неточно, то, в зависимости от погрешности представления констант 2. и 0.1 и от точности вычислений, этот цикл выполнится или 21 раз (как нам нужно) или 20 раз (получено при просчете)!

Чтобы избежать этой ошибки, нужно либо учесть эту погрешность (например, задать в качестве границы не 2., а что-то вроде 2.0001), либо (что предпочтительней) использовать цикл по целой переменной, например:

double a;

int i;

. . .

for (i=0; i<21; i++) { a=i*0.1;. . .}

или вариант

double a; int i;

. . .

for (i=0, a=0.; i<21; i++, a+=0.1) { . . . }

5. Нерациональное использование директивы #define.

Часто нерационально используют директиву препроцессора #define. Например, конструкция

#define PI 3.14159265

. . .

a=sin(PI/t1)+2.*cos(PI+t2)*sin(PI/t3)+cos(PI*t4);

приведет к нерациональному расходования памяти машины. Везде, где указано PI будет подставлено значение 3.14159265, то есть константа типа double, занимающая 8 байт памяти. Всего будет занято 32 байта (4 раза использована конструкция PI) памяти. Рациональнее здесь использовать конструкцию

double PI=3.14159265;

. . .

a=sin(PI/t1)+2.*cos(PI+t2)*sin(PI/t3)+cos(PI*t4);

так как при указании PI здесь будет использован адрес переменной, который в зависимости от модели памяти будет занимать от 2 байт (near) до 4 байт (far), а сама переменная занимает 8 байт (значение типа double). Значит всего мы используем 2*4+8=16 или 4*4+8=24 байта памяти.

6. Нерациональная запись арифметических выражений.

Если, например, записано выражение

d=2*a+(b+1)*4-c;

где a, b, c, d имеют тип double, то это приведет к нерациональному расходованию вычислительной мощности машины. Рассмотрим, как будет вычисляться это выражение. Вначале будет вычислено 2*a, причем константа 2 целого типа будет преобразована к типу double (по времени это почти эквивалентно умножению с плавающей точкой), то же самое будет произведено с константами 1 и 4 целого типа. То есть в ходе выполнения этого оператора мы получили три лишние операции преобразования типа. Чтобы этого не было, надо было записать

d=2.*a+(b+1.)*4.-c;

В этом случае все данные имеют тип double и операций преобразования типа не будет, поэтому это выражение будет вычислено намного быстрее.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]