Скачиваний:
2
Добавлен:
03.01.2024
Размер:
2.1 Mб
Скачать

Область видимости переменных

Область действия (видимости) переменной – это правила,

которые устанавливают, какие данные доступны из данного места программы.

Сточки зрения области действия переменных различают три

типа переменных:

глобальные

локальные

формальные параметры.

Глобальные переменные:

Это переменные, объявленные вне какой-либо функции.

Могут быть использованы в любом месте программы, но перед их использованием они должны быть объявлены.

Область действия глобальной переменной – вся программа.

Локальные переменные:

Это переменные, объявленные внутри функции.

Локальная переменная доступна внутри блока, в котором она объявлена.

Локальная переменная существует пока выполняется блок, в котором эта переменная объявлена. При выходе из блока эта переменная (и ее значение) теряется.

Формальные параметры:

Используются в теле функции так же, как локальные переменные.

Область действия формальных параметров - блок, являющийся телом функции.

Недостатки использования глобальных переменных:

Они занимают память в течение всего времени работы программы.

Делает функции менее общими и затрудняет их использование в других программах.

Использование внешних переменных делает возможным появление ошибок из-за побочных явлений.

Эти ошибки, как правило, трудно отыскать.

Параметры-значения:

Все аргументы функции передаются по значению

При вызове функции в стеке выделяется место для формальных параметров функции, и туда заносится значение фактического параметра, т. е. значение параметра при вызове функции.

Далее функция использует и меняет значения в стеке. При выходе из функции измененные значения параметров теряются.

В языке Си вызванная функция не может изменить переменные, указанные в качестве фактических параметров в функции при обращении к ней.

Параметры-ссылки:

Для возможности изменения внутри функции значений переменных, являющихся параметрами этой функции, необходимо передавать в функцию не значения этих переменных, а их адреса

11

Структура программы с использованием функций

Глобальные переменные

Функция А

Локальные переменные

{

...

}

Функция В

Локальные переменные

{

...

}

Главная (main)

{

Вызов А ... Вызов А ... Вызов В ...

...

}

Глобальными называются переменные, которые описаны в главной программе.

Время жизни глобальных переменных — с начала программы и до ее завершения.

Располагаются в сегменте данных.

В функциях описываются локальные переменные.

Они располагаются в сегменте стека, причем распределение памяти происходит в момент вызова подпрограммы, а ее освобождение — по завершении подпрограммы.

Локальные переменные автоматически не обнуляются.

Обмен данными:

Через глобальные переменные

Через параметры Параметры передаются:

По значению

По адресу (по указателю)

12

Область видимости переменных. Упрощенная структура исполняемого файла (приложения) в ОП

Статические данные распределены в специальном

статическом сегменте памяти программы

 

 

Сегмент данных

 

 

Динамическая память

 

 

 

 

 

 

 

 

 

 

Сегмент стека

 

 

 

 

 

 

 

 

 

 

 

 

 

Динамические

 

Сегмент кода

данные

 

 

 

 

 

 

Глобальные данные объявленные вне функций.

Данные, объявленные внутри функций как static

статические локальные данные:

o доступны только функции, в которой описаны, но существуют (занимают память) во время выполнения всей программы.

1 Мбайт

Локальные

1 Мбайт

данные

Код программы

Стек – область памяти, в которой хранятся локальные переменные и адреса возврата

Локальные переменные,

объявленные внутри функций

BSS-сегмент (block started by symbol) содержит неинициализированные глобальные переменные, или статические переменные без явной инициализации.

Этот сегмент начинается непосредственно за data-сегментом.

Обычно загрузчик программ инициализирует bss область при загрузке приложения

нулями.

Дело в том, что в data области переменные инициализированы – то есть затирают своими значениями выделенную область памяти.

Так как переменные в bss области не инициализированы явно, то они теоретически могли бы иметь значение, которое ранее хранилось в этой области, а это уязвимость, которая предоставляет доступ до приватных (возможно) данных.

Поэтому загрузчик вынужден обнулять все значения.

За счёт этого и неинициализированные глобальные переменные, и статические

переменные по умолчанию равны нулю.

13

2. Функции. Механизм вызова

Вызов ПП делится на:

Подготовительные служебные действия вызывающей программы (накладные временные расходы)

Собственно работу ПП

Заключительные служебные действия вызывающей программы (накладные временные расходы)

Каждому вызову ПП соответствует отдельная область памяти стековый кадр

Стековый кадр:

Существует на протяжении всего вызова ПП:

Включая вложенные вызовы других ПП

Перестаёт существовать после завершения вызова ПП

Стековым кадром могут пользоваться:

Программа

ПП

Другие ПП, вызванные из ПП

Стековый кадр содержит:

Адрес возврата – адрес команды, которая получит управление после завершения работы (выхода) из ПП:

Вычисляется процессором или компилятором

Обычно адрес первой команды заключительных действий

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

Могут частично находиться в регистрах процессора

Внутренние переменные ПП

Результат работы ПП – ячейка памяти, значение которой устанавливается ПП перед выходом и после этого может использоваться вызывающей программой:

Может находиться в регистре процессора

Пример стековых кадров:

void f(int *px) { *px = 1; }

int g() { int x; f(&x);

return x;

}

int main () { int x = g(); return 0;

}

int main()

{

int a[10], i;

for (i=0;i<=10;i++) a[i]=0;

return 0;

}

Вызов функции – это

оператор.

У вызова функции есть приоритет – один из самых высоких.

Список аргументов функции считают за один операнд, так что оператор оказывается бинарным (первый операнд – сама функция, второй – список ее аргументов).

14

Формальные и фактические параметры

При объявлении функции указываются формальные параметры, которые потом используются внутри самой функции.

При вызове функции мы используем фактические параметры.

Фактическими параметрами могут быть переменные любого подходящего типа или константы.

Пример:

Пусть есть функция, которая возвращает квадрат числа и функция, которая суммирует два числа (см. код справа).

Обратите внимание, что приведение типов происходит неявно и только тогда, когда это возможно.

Если функция получает число в качестве аргумента, то нельзя ей передать переменную строку, например "20" и т.д.

Вообще, лучше всегда использовать верный тип или явно приводить тип к нужному.

Передача аргументов

При передаче аргументов происходит их копирование.

Это значит, что любые изменения, которые функция производит над переменными, имеют место быть только внутри функции.

Пример: См. код ниже.

#include <stdio.h>

void main()

 

void change(int a)

{

 

 

 

{

int d = 200;

 

a = 100;

printf("%d\n", d);

 

 

 

 

printf("%d\n", a);

change(d);

 

 

}

printf("%d", d);

 

 

 

getch();

 

 

 

 

 

}

 

 

 

 

 

 

 

 

#include <stdio.h>

/* Формальные параметры имеют имена a и b

по ним мы обращаемся к переданным аргументам внутри функции */ int sum(int a, int b)

{

return a+b;

}

float square(float x)

{

return x*x;

}

void main()

{/* Фактические параметры могут иметь любое имя, в том числе и не иметь имени */

int one = 1; float two = 2.0;

/* Передаём переменные, вторая переменная приводится к нужному типу */

printf("%d\n", sum(one, two));

// Передаём числовые константы printf("%d\n", sum(10, 20));

/* Передаём числовые константы неверного типа, они автоматически приводится к нужному */

printf("%d\n", sum(10, 20.f));

// Переменная целого типа приводится к типу с плавающей точкой printf("%.3f\n", square(one));

/* В качестве аргумента может выступать и вызов функции, которая возвращает нужное значение */

printf("%.3f\n", square(sum(2 + 4, 3))); getch();

}

15

Передача аргументов

1й способ: передача по значению

#include <stdio.h>

void main()

 

void change(int a)

{

 

 

 

{

int d = 200;

 

a = 100;

printf("%d\n", d);

 

 

 

 

printf("%d\n", a);

change(d);

 

 

}

printf("%d", d);

 

 

 

getch();

 

 

 

 

 

}

 

 

 

 

 

 

 

 

Внутри функции мы работаем с переменной а, которая является копией переменной d.

Мы изменяем локальную копию, но сама переменная d при этом не меняется.

После выхода из функции локальная переменная будет уничтожена.

Переменная d при этом никак не изменится.

2й способ: передача по указателю

#include <stdio.h> void change(int *a)

{

*a = 100; printf("%d\n", *a);

}

void main()

{

int d = 200; printf("%d\n", d); change(&d); printf("%d", d); getch();

}

Каким образом тогда можно изменить переменную?

Для этого нужно передать адрес этой переменной. Перепишем функцию, чтобы она принимала указатель типа int:

Здесь также была создана локальная переменная, но так как передан был адрес, то мы изменили значение переменной d, используя её адрес в оперативной памяти.

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

Простое правило: если вы хотите изменить переменную, необходимо передавать функции указатель на эту переменную.

Следовательно, чтобы изменить указатель, необходимо передавать указатель на указатель и т.д.

NB: Этот способ рассмотреть позже, после лекции по

указателям

16

Передача аргументов

 

Пример: напишем функцию, которая будет принимать

Для изменения объекта необходимо передавать указатель

размер массива типа int и создавать его.

 

на него, в данном случае – указатель на указатель:

 

 

 

 

 

 

 

 

 

 

#include <stdlib.h>

 

 

 

 

#include <stdlib.h>

 

void init(int *a, unsigned size)

 

 

 

 

void init (int **a, unsigned size)

 

{

 

 

 

 

 

 

{

 

a = (int*) malloc(size * sizeof(int));

 

Эта функция выведет ERROR.

 

*a = (int*) malloc(size * sizeof(int));

 

}

 

 

 

Мы передали адрес переменной.

 

}

 

 

 

 

 

Внутри функции init была создана

 

void main()

 

void main()

 

локальная переменная a, которая

 

{

 

{

 

 

 

хранит адрес массива.

 

int *a = NULL;

 

int *a = NULL;

 

После выхода из функции эта локальная

 

init(&a, 100);

 

init(a, 100);

 

переменная была уничтожена.

 

if (a == NULL)

 

if (a == NULL)

 

Кроме того, что мы не смогли добиться

 

printf("ERROR");

 

{

 

 

 

нужного результата, у нас обнаружилась

 

else

 

printf("ERROR");

 

утечка памяти: была выделена память

 

{

 

} else {

 

на куче, но уже не существует

 

printf("OKAY...");

 

printf("OKAY...");

 

переменной, которая бы хранила адрес

 

free(a);

 

free(a);

 

этого участка.

 

 

}

 

}

 

 

 

 

 

 

getch();

 

 

 

 

 

 

getch();

 

 

 

 

 

 

}

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

NB: Этот способ рассмотреть позже, после лекции по указателям

17

Передача аргументов по значению

Пример: Обмен значениями переменных

#include <stdio.h> //прототип функции void swap(int a, int b);

/* Переменные, определенные вне блока, являются глобальными переменными */

int x = 5, y = 10;

void main()

{

printf ("At the beginning x = %d and y = %d\n", x, y); swap(x, y);

printf ("Now x = %d and y = %d\n", x, y); return 0;

}

void swap(int a, int b) //реализация функции

{

int tmp; tmp = a; x = b;

y = tmp;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

x

 

y

 

 

int x = 5, y = 10;

 

 

 

 

 

 

 

5

 

10

 

 

 

 

main ()

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

printf (“Вначале x = %d и y = %d\n”, x, y);

 

 

 

 

 

 

swap(x, y);

 

 

 

 

 

 

 

printf (“Теперь x = %d и y = %d\n”, x, y);

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

a

 

b

temp

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

 

10

5

 

 

 

 

 

 

10

 

5

 

 

 

 

 

void swap(int a, int b)

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

int tmp;

 

 

 

 

 

 

 

 

tmp = a;

 

 

 

 

 

 

 

 

a = b;

 

 

 

 

 

 

 

 

b = tmp;

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

18

 

 

 

 

 

 

 

 

 

Передача аргументов по указателю

Для возможности изменения внутри функции значений переменных, являющихся параметрами этой функции, необходимо передавать в функцию не значения этих переменных, а их адреса

#include <stdio.h>

void swap1(int *a, int *b); // прототип функции void main( )

{

int x = 5, y = 10;

printf ("At the beginning x = %d and y = %d\n", x, y); swap1 (&x, &y);

printf ("Now x = %d and y = %d\n", x, y);

}

void swap1 (int *a, int *b) // реализация функции

{

int tmp; tmp = *a; *a = *b; *b = tmp;

}

адрес1 адрес2

main ()

 

x

y

 

 

 

 

 

 

 

 

5

 

10

{

 

 

 

10

 

5

int x = 5, y = 10;

 

 

 

 

 

 

 

 

printf (“Вначале x = %d и y = %d\n”, x, y); swap1 (&x, &y);

printf (“Теперь x = %d и y = %d\n”, x, y);

}

a

b

temp

 

 

 

 

 

 

адрес1

адрес2

5

 

 

 

void swap1 (int *a, in t*b)

{

int tmp; tmp = *a; *a = *b; *b = tmp;

}

NB: Этот способ рассмотреть позже, после лекции по указателям

19

 

 

3. Передача массивов в функцию

Передача массива в качестве аргумента

 

 

 

Передача матриц в функцию через параметры

 

Если в качестве аргумента функции используется массив, то

 

Для передачи матрицы в функцию в качестве параметров

 

необходимо в качестве формального параметра передать

нужно указать:

 

адрес начала массива.

 

 

адрес начала вспомогательного массива указателей на

 

Адрес любого другого элемента массива можно вычислить по

 

начала срок матрицы

 

его индексу и типу элементов массива.

 

 

размерность матрицы: количество строк и столбцов

 

Имя массива подменяется на указатель, поэтому передача

 

 

 

 

 

 

 

одномерного массива эквивалентна передаче указателя.

 

 

 

#include <stdio.h>

 

Правило подмены массива на указатель не рекурсивное. Это

 

 

 

void printArray(int arr[][5], unsigned size)

 

значит, что необходимо указывать размерность двумерного

 

 

 

// или: void printArray(int (*arr)[5], unsigned size)

 

массива при передаче

 

 

 

 

 

 

 

 

 

 

 

{

 

 

 

Пример: функция получает массив и его размер и выводит

 

 

 

 

 

 

 

 

 

unsigned i, j;

 

на печать:

 

 

 

 

 

for (i = 0; i < size; i++)

 

#include <stdio.h>

 

 

 

 

 

{

 

 

 

#define n 10

 

 

 

 

 

for (j = 0; j < 5; j++)

 

void printArray(int *arr, unsigned size)

 

 

 

 

 

printf("%d ", arr[i][j]);

 

// или: void printArray(int arr[], unsigned size)

 

 

 

 

 

printf("\n");

 

{

 

 

 

 

 

 

 

}

 

 

 

unsigned i;

 

 

 

 

 

}

 

 

 

for (i = 0; i < size; i++)

 

 

 

 

 

void main()

 

printf("%d ", arr[i]);

 

 

 

 

 

{

 

 

 

}

 

 

 

 

 

 

 

int x[][5] =

 

void main()

 

 

 

 

 

{

 

 

 

{

 

 

 

NB: Рассмотреть

 

 

{ 1, 2, 3, 4, 5},

 

 

 

int x[n] = {1, 2, 3, 4, 5};

 

позже, после лекции

 

 

{ 6, 7, 8, 9, 10}

 

 

 

printArray(x, n);

 

 

 

};

 

 

 

 

по массивам и

 

 

 

 

 

 

getch();

 

 

 

 

printArray(x, 2);

 

 

 

 

 

 

 

 

 

}

 

 

 

указателям

 

 

 

getch();

 

 

 

 

 

 

 

 

 

 

 

}

 

 

20

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Соседние файлы в папке Лекции