Массивы и строки
Массивы
Массив — подряд идущие в памяти элементы одного типа.
int a[5]; /* 5 int, значения не определены (если локальный) */
int b[5] = {1, 2, 3, 4, 5};
int c[5] = {1, 2}; /* {1, 2, 0, 0, 0} */
int d[] = {7, 8, 9}; /* размер 3 вычислен по инициализатору */
double z[70] = {0}; /* все нули */
size_t n = sizeof(b) / sizeof(b[0]); /* число элементов: 5 */
Индексы начинаются с нуля: у массива из N элементов допустимы индексы 0…N−1.
Нет проверки границ
b[5] и b[-1] компилируются без ошибок. Это главный источник уязвимостей в программах на C (переполнение буфера).
Массивы и функции
Массив в функцию передаётся как указатель на первый элемент. Поэтому размер передают отдельно:
double dot(const double *a, const double *b, int n)
{
double s = 0.0;
for (int i = 0; i < n; i++)
s += a[i] * b[i];
return s;
}
Запись double a[] в параметре — то же самое, что double *a.
Массивы и страницы памяти
Раз элементы лежат подряд, массив может пересекать границу страницы. Посчитаем на примере задачи 7: 70 элементов double = 560 байт, страница 512 байт.
| Где начинается массив | Сколько страниц занимает |
|---|---|
| ровно с начала страницы | 2 (512 + 48 байт) |
со смещения 504 (последний double страницы) |
3 (8 + 512 + 40 байт) |
Минимум страниц для N байт — ⌈N / P⌉, максимум — ⌈N / P⌉ + 1 (если массив «неудачно» начинается в конце страницы). Это ключ к поиску минимального и максимального ответа.
Двумерные массивы
int m[3][4]; /* 3 строки по 4 элемента, хранятся построчно */
m[1][2] = 5; /* адрес: m + (1*4 + 2) * sizeof(int) */
Обход по строкам (for i { for j { m[i][j] } }) идёт по памяти подряд — быстро. Обход по столбцам прыгает через строку — для больших массивов это много промахов кеша и TLB.
Строки
Строка в C — массив char, заканчивающийся нулевым байтом '\0'.
char s[] = "fork"; /* {'f','o','r','k','\0'} — 5 байт */
const char *p = "exec"; /* указатель на строковую константу (только чтение!) */
printf("%s %zu %zu\n", s, sizeof(s), strlen(s)); /* fork 5 4 */
Функции из <string.h>:
| Функция | Действие |
|---|---|
strlen(s) |
длина без '\0' |
strcpy(dst, src) / strncpy |
копирование (опасно без контроля размера) |
snprintf(buf, size, fmt, ...) |
безопасная форматированная запись в буфер |
strcmp(a, b) |
сравнение: 0 — равны |
strcat, strchr, strstr |
склейка, поиск символа, поиск подстроки |
memcpy, memset, memcmp |
работа с произвольными байтами |
Сравнение строк
if (s == "fork") сравнивает адреса, а не содержимое. Правильно: if (strcmp(s, "fork") == 0).
Почему строковую константу нельзя менять?
Литерал "exec" компоновщик кладёт в сегмент только для чтения (.rodata). Страницы этого сегмента отображены без права записи, поэтому p[0] = 'E' вызовет страничное исключение защиты, и ядро пошлёт процессу SIGSEGV. Массив char s[] = "fork" — копия литерала в стеке, его менять можно.
Итог
- Массив — элементы подряд; индексы с 0; границы не проверяются.
- В функцию массив передаётся как указатель, размер — отдельно.
- Массив размера N байт занимает ⌈N/P⌉ или ⌈N/P⌉+1 страниц размера P.
- Строка — массив
charс завершающим'\0'; сравнение —strcmp.
Практика решено 0 из 5
Сколько байт занимает строка char s[] = "fork";?
Что напечатает фрагмент?
int a[5] = {1, 2};
printf("%d %d\n", a[1], a[4]);
Напишите функцию double sum(const double *a, int n), возвращающую сумму n элементов массива.
double sum(const double *a, int n)
{
double s = 0.0;
for (int i = 0; i < n; i++)
s += a[i];
return s;
}
Что происходит при обращении к a[10], если массив объявлен как int a[10]?
Какие утверждения о массиве-параметре void f(int a[]) верны?
Войдите, чтобы отмечать пройденные уроки.