Функции, области видимости и классы памяти
Функции
#include <stdio.h>
double dot(const double *a, const double *b, int n); /* объявление (прототип) */
int main(void)
{
double x[3] = {1, 2, 3}, y[3] = {4, 5, 6};
printf("%f\n", dot(x, y, 3));
return 0;
}
double dot(const double *a, const double *b, int n) /* определение */
{
double sum = 0.0;
for (int i = 0; i < n; i++)
sum += a[i] * b[i];
return sum;
}
- Объявление (прототип) сообщает компилятору имя, типы параметров и результата. Обычно прототипы лежат в заголовочных файлах
.h. - Определение содержит тело.
voidвместо типа результата — функция ничего не возвращает.
Передача по значению
В C аргументы копируются. Функция не может изменить переменную вызывающего, если не получит её адрес:
void reset(int *p) { *p = 0; }
int x = 5;
reset(&x); /* x == 0 */
Так работают scanf("%d", &n), wait(&status), pipe(fds) — системные функции возвращают результаты через указатели.
Стек вызовов
При каждом вызове функции на стеке создаётся кадр (frame): адрес возврата, параметры, локальные переменные. При выходе кадр уничтожается.
Нельзя возвращать адрес локальной переменной
После выхода из функции её кадр освобождается, и указатель на локальную переменную указывает на мусор, который перезапишет следующий вызов.
Области видимости и время жизни
| Где объявлена | Видимость | Время жизни | Память |
|---|---|---|---|
внутри блока {} |
до конца блока | до выхода из блока | стек (или регистр) |
| вне функций | весь файл (и другие файлы через extern) |
вся программа | сегмент данных |
static внутри функции |
эта функция | вся программа | сегмент данных |
static вне функций |
только этот файл | вся программа | сегмент данных |
Классы памяти: static, extern, register
/* file1.c */
double *a; /* определение глобальной переменной */
static int hidden; /* видна только в file1.c */
/* file2.c */
extern double *a; /* объявление: «a определена в другом файле» */
void f(void)
{
static int calls = 0; /* сохраняется между вызовами */
register int i; /* просьба держать i в регистре */
calls++;
}
Код из задачи 7
В условии: extern double *a; extern double *b; register int i; register double sum = 0.0;. Читаем так: i и sum — в регистрах, обращений к памяти не дают. А a и b — глобальные переменные-указатели где-то в памяти. Чтобы вычислить a[i], процессор сначала читает значение указателя a из памяти, а затем элемент массива. Раз a и b объявлены extern, они могут быть определены в разных файлах и лежать на разных страницах.
const и volatile
const int MAX = 100; /* нельзя изменить */
void print(const char *s); /* функция обещает не менять строку */
volatile int flag; /* компилятор не должен кешировать значение в регистре:
его может изменить обработчик сигнала или другой процесс */
volatile важен для переменных, которые меняет обработчик сигнала (тип volatile sig_atomic_t), и для памяти, общей с другими процессами. Но volatile не делает операции атомарными — для синхронизации нужны другие средства.
Итог
- Аргументы передаются по значению; чтобы функция изменила переменную, передайте адрес.
- Локальные переменные живут в стеке до выхода из функции; глобальные и
static— всю программу. extern— объявление переменной из другого файла,register— переменная в регистре (не память).volatileзапрещает кеширование в регистре, но не защищает от гонок.
Практика решено 0 из 6
Что напечатает программа?
void inc(int x) { x++; }
int main(void) {
int a = 1;
inc(a);
printf("%d\n", a);
return 0;
}
Что напечатает программа после трёх вызовов f()?
int f(void) {
static int n = 0;
n++;
return n;
}
int main(void) {
f(); f();
printf("%d\n", f());
}
Что означает extern double *a; в задаче про TLB?
Что верно про register int i;?
Напишите функцию swap, которая меняет местами значения двух целых переменных по указателям:
void swap(int *x, int *y).
void swap(int *x, int *y)
{
int tmp = *x;
*x = *y;
*y = tmp;
}
Где хранится каждая переменная? Отметьте область памяти процесса.
| Стек | Сегмент данных (.data/.bss) | Куча | |
|---|---|---|---|
| локальная int x внутри функции | |||
| глобальная int count | |||
| static int n внутри функции | |||
| память, полученная malloc(100) | |||
| параметр функции (если не в регистре) |
Войдите, чтобы отмечать пройденные уроки.