---
tags: прога, зачёт по проге
---
# Задачи с зачёта (2020-12-10, четверг)
**АХТУНГ:** копипаст кода отсюда тождественно равно получению пермабана. Семинарист мой код спалит моментально — быстрее, чем свет до него дойдёт, от листочка отразившись. Никто так не упарывается с красотой и декомпозицией.
Весь код даётся только в образовательных целях, чтобы понять, куда рыть. Алгоритмы вы будете писать на нелинованных листочках A4 ручкой, простыню убьётесь копировать.
**ДИСКЛЕЙМЕР:** Ответы могут быть несущественно, заметно, разительно или полностью и в корне неверными. Если ошибки есть, то виноваты все, кроме меня. Но можете написать, и я исправлю.
## Задача 1. Область видимости и время жизни
> Классифицируйте виды переменных по области видимости и по времени жизни. Приведите примеры.
### Времена жизни переменных
Время жизни переменной — это промежуток времени, когда для переменной выделена память. Время жизни тесно связано с видом хранения объекта.
- **Автоматическое**: начинается с момента входа в блок, в котором определена переменная, и завершается после выхода из него.
```c
int main() {
int x = 42; // автоматическое хранение
return 0;
}
```
- **Статическое**: начинается до начала исполнения программы и длится до её завершения.
```c
// вне функций
static int x = 42;
// Если static внутри блока, то срок жизни - от первого
// захода в блок до завершения работы программы
int main() {
static int y = 24;
return 0;
}
// Например, скрок жизни a - ноль
void func() {
if (false) {
static int a = 5;
}
}
```
- **Потоковое**: на протяжении работы потока.
```c
// Это можно пропустить, если скажете - ожидайте
// доп.вопросов про потоки
thread_local int my_id = 0;
```
### Область видимости переменных
- **Блок**: внутри блока, в том числе во вложенных блоках, с момента объявления.
```c
int main() {
int x = 0; // виден в блоке
return 0;
}
```
- **Файл**: внутри единицы трансляции с момента объявления.
```c
static int x = 42;
int main() {
printf("%d\n", x);
return 0;
}
```
```c
// Например, если в code.c написать
#include "code.h"
static int c = 1;
// то в main.c нельзя будет ею пользоваться
int main() {
extern int c;
c++;
}
// Но если написать в code.c просто
#include "code.h"
int c = 1;
// То в main.c код выше будет исполняться
```
### Референсы
- https://en.cppreference.com/w/c/language/scope
- https://en.cppreference.com/w/c/language/storage_duration
## Задача 2. Типизация
> Что такое типизация? Какие проверки типизации делает компилятор? Приведите примеры ошибок типизации.
Типизация — классификация значений по типам. Тип — множество значений и операции над ними. В C тип объекта определяет, как интерпретировать значение в памяти.
Компилятор узнаёт, можно ли выполнить операцию над объектом в коде, проверяя тип объекта. Например, нельзя присвоить в переменную типа стракта число:
```c
struct test {
int value;
};
int main() {
struct test my_struct = 42;
return 0;
}
```
Другой пример: попытка использовать `->` для чего-то, кроме указателя:
```c
struct test my_struct;
my_struct->value = 42;
```
## Задача 3. Дополнительный код
> Даны два signed char, в один присвоили -72, в другой -113. Запишите их в двоичной системе счисления и в шестнадцатеричной. Приведите вычисления.
- `-72` = `0b10111000` = `0xb8`
1. Переведём `72` в двоичную систему счисления: `0b01001000`.
2. Инвертируем все биты: `0b10110111`.
3. Прибавим единичку: `0b10111000`.
- `-113` = `0b10001111` = `0x8f`
1. Переведём `113` в двоичную систему счисления: `0b01110001`.
2. Инвертируем все биты: `0b10001110`.
3. Прибавим единичку: `0b10001111`.
## Задача 4. Запись сложных типов
> Запишите следующие типы (без использования `typedef`):
> - указатель на константный указатель на целое число
> - ???
> - указатель на массив указателей на целое число
> - указатель на любую функцию
> - указатель на функцию, принимающую указатель на функцию, которая возвращает указатель на функцию
> > Не совсем так
> >
> > > Возможно. [name=slowlime]
```c
int *const *one;
int *(*three)[];
int (*four)();
void (*five)(void (*(*)())());
```
### Референсы
- https://hackmd.io/@slowlime/B1esMNpiv#1-%D0%A7%D1%82%D0%B5%D0%BD%D0%B8%D0%B5-%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D0%B3%D0%BE-%D1%82%D0%B8%D0%BF%D0%B0-%D0%B2-%D0%A1%D0%B8
- https://en.cppreference.com/w/c/language/declarations
## Задача 5. Чтение сложных типов
> Опишите словами следующий тип:
> ```c
> int (*(*f(void))[])(void);
> ```
`f` — это функция с 0 параметров, возвращающая указатель на массив указателей на функции с 0 параметров и возвращающие `int`.
## Задача 6. Бинарный поиск
> Напишите двоичный поиск. Код должен уметь работать с любыми типами данных.
> Приведите пример вызова для int, double, строк и произвольных структур.
```c
void const *bsearch(void const *array, void const *key,
size_t size, size_t len,
int (*comparator)(void const *, void const *)) {
assert(array != NULL);
assert(key != NULL);
assert(size > 0);
assert(comparator != NULL);
char const *bytes = array;
size_t left = 0;
size_t right = len;
while (left < right) {
size_t middle = left + (right - left) / 2;
void const *element = (void const *) bytes[middle * size];
int cmp = comparator(element, key);
if (cmp < 0) {
left = middle + 1;
} else if (cmp > 0) {
right = middle;
} else {
return element;
}
}
return NULL;
}
int int_cmp(void const *left_ptr, void const *right_ptr) {
assert(left_ptr != NULL);
assert(right_ptr != NULL);
int left = *(int const *) left_ptr;
int right = *(int const *) right_ptr;
if (left < right) {
return -1;
} else if (left > right) {
return 1;
}
return 0;
}
int string_cmp(void const *left_ptr, void const *right_ptr) {
assert(left_ptr != NULL);
assert(right_ptr != NULL);
char const *left = *(char const **) left_ptr;
char const *right = *(char const **) right_ptr;
return strcmp(left, right) == 0;
}
int double_cmp(void const *left_ptr, void const *right_ptr) {
assert(left_ptr != NULL);
assert(right_ptr != NULL);
double left = *(double const *) left_ptr;
double right = *(double const *) right_ptr;
if (left < right) {
return -1;
} else if (left > right) {
return 1;
}
return 0;
}
struct test {
int value;
};
int struct_cmp(void const *left_ptr, void const *right_ptr) {
assert(left_ptr != NULL);
assert(right_ptr != NULL);
struct test const *left = (struct test const *) left_ptr;
struct test const *right = (struct test const *) right_ptr;
if (left->value < right->value) {
return -1;
} else if (left->value > right->value) {
return 1;
}
return 0;
}
int main() {
int int_array[] = {1, 2, 3, 4, 5};
printf("int_array: %d\n", *(int const *) bsearch(
int_array, &int_array[2],
sizeof(int_array[0]), sizeof(int_array) / sizeof(int_array[0]),
int_cmp
));
double double_array[] = {1., 2., 3., 4., 5.};
printf("double_array: %lf\n", *(double const *) bsearch(
double_array, &double_array[2],
sizeof(double_array[0]), sizeof(double_array) / sizeof(double_array[0]),
double_cmp
));
char const *string_array[] = {"hello", "goodbye", "world", "space"};
printf("string_array: %s\n", *(char const **) bsearch(
string_array, &"world",
sizeof(string_array[0]), sizeof(string_array) / sizeof(string_array[0]),
string_cmp
));
struct test struct_array[] = {{1}, {2}, {3}, {4}, {5}};
printf("struct_array: {%d}\n", ((struct test *) bsearch(
struct_array, &struct_array[2],
sizeof(struct_array[0]), sizeof(struct_array) / sizeof(struct_array[0]),
struct_cmp
))->value);
return 0;
}
```
## Задача 7. Очередь
> Реализуйте очередь на списке.
```c
typedef struct list {
int value;
struct list *next;
} list_t;
list_t *list_new() {
return NULL;
}
bool list_insert_after(list_t **self, int value) {
assert(self != NULL);
list_t *node = malloc(sizeof(list_t));
if (node == NULL) {
return false;
}
node->value = value;
if (*self != NULL) {
node->next = (*self)->next;
(*self)->next = node;
} else {
node->next = NULL;
}
*self = node;
return true;
}
bool list_decapitate(list_t **self) {
assert(self != NULL);
if (*self == NULL) {
return false;
}
list_t *head = *self;
list_t *next = head->next;
free(head);
*self = next;
return true;
}
typedef struct {
list_t *start;
list_t *end;
size_t len;
} queue_t;
queue_t queue_new() {
queue_t result = {
.start = list_new(),
};
result.end = result.start;
return result;
}
bool queue_is_empty(queue_t const *self) {
assert(self != NULL);
return self->len == 0;
}
bool queue_enqueue(queue_t *self, int value) {
assert(self != NULL);
if (!list_insert_after(&self->end, value)) {
return false;
}
if (queue_is_empty(self)) {
self->start = self->end;
}
++self->len;
return true;
}
bool queue_dequeue(queue_t *self, int *result) {
assert(self != NULL);
if (queue_is_empty(self)) {
return false;
}
int value = *list_get_value(self->start);
if (!list_decapitate(&self->start)) {
return false;
}
if (result != NULL) {
*result = value;
}
if (self->len == 1) {
self->end = self->start;
}
--self->len;
return true;
}
```
Этот код я писал вручную и на листочке. Надеюсь, понятно, чем я 2 часа занимался.
### Референсы
- https://hackmd.io/@slowlime/B1esMNpiv#2-BFS
## Задача 8. Successor в дереве
> Напишите функцию, которая возвращает следующий за данным элемент дерева при инфиксном обходе. Считайте, что узлы хранят указатель на родителя.
```c
typedef struct node {
struct node *parent;
struct node *left;
struct node *right;
} node_t;
bool is_left_child(node_t const *node) {
return node->parent != NULL && node->parent->left == node;
}
node_t *successor(node_t *node) {
if (node == NULL) {
return NULL;
}
if (node->right != NULL) {
node_t *leftmost_node = node;
while (leftmost_node->left != NULL) {
leftmost_node = leftmost_node->left;
}
return leftmost_node;
}
node_t *result_child = node;
while (result_child->parent != NULL) {
if (is_left_child(result_child->parent)) {
return result_child->parent;
}
result_child = result_child->parent;
}
return NULL;
}
```
## Задача 9. Анаграммы
> Напишите функцию, которая проверяет, являются ли две строки анаграммами друг друга.
Две строки — анаграммы, если они состоят из тех же символов в том же количестве.
```c
bool is_anagram(char const *left, char const *right) {
assert(left != NULL);
assert(right != NULL);
size_t count_left[256] = {0};
size_t count_right[256] = {0};
memset(count_left, 0, sizeof(count_left));
memset(count_right, 0, sizeof(count_right));
char const *lptr = left;
char const *rptr = right;
while (true) {
if (*lptr == '\0' && *rptr == '\0') {
break;
}
if (*lptr == '\0' || *rptr == '\0') {
return false;
}
++count_left[*(lptr++)];
++count_right[*(rptr++)];
}
for (size_t i = 0; i < 256; ++i) {
if (count_left[i] != count_right[i]) {
return false;
}
}
return true;
}
```
## Задача 10. Связывание
> В `code.c` объявляются две функции: `f`, `g` — и две глобальные переменные: `a`, `b`. `f` и `a` должны быть видны из других единиц трансляции; `g`, `b` нужно скрыть, чтобы их не было видно из других единиц трансляции. Приведите код `code.c` и `code.h`.
Код `code.c`:
```c
void f();
static void g();
int a;
static int b;
```
Код `code.h`:
```c
#pragma once
void f();
extern int a;
```
### Референсы
- https://en.cppreference.com/w/c/language/storage_duration#Linkage
## Задача 11. Поиск ошибок в коде
> Отметьте ошибки в коде и прокомментируйте их.
>
> ```c
> #define MAX(a, b) a < b ? a : b;
>
> void main() {
> char *str = malloc(1);
> int i; char c; int ws_counter;
>
> while (c = getchar() != EOF) {
> str[++i] = c;
> str = realloc(str, i);
> }
>
> printf(str);
>
> for (i = 0; i < strlen(str); ++i); {
> ws_counter += str[i] == ' ';
> }
>
> int limit;
> print("Enter whitespace limit:\n");
> scanf("%d", limit);
>
> /* do The Check */
> if (MAX(limit, ws_counter) == ws_counter) {
> printf("Too much!\n");
> } else if (MAX(limit, ws_counter) == limit) {
> printf("Not enough!\n");
> } else if (limit == ws_counter) {
> printf("OK!\n");
> }
> };
> ```
Строки с ошибкой отмечены числами в кружочке.
```c
#define MAX(a, b) a < b ? a : b; // ①
void main() { // ②
char *str = malloc(1);
int i; char c; int ws_counter; // ③
while (c = getchar() != EOF) { // ④
str[++i] = c; // ⑤
str = realloc(str, i); // ⑥
}
// ⑦
printf(str); // ⑧
for (i = 0; i < strlen(str); ++i); { // ⑨
ws_counter += str[i] == ' ';
}
int limit; // ⑩
print("Enter whitespace limit:\n"); // ⑪
scanf("%d", limit); // ⑫
/* do The Check */
if (MAX(limit, ws_counter) == ws_counter) { // ⑬
printf("Too much!\n");
} else if (MAX(limit, ws_counter) == limit) {
printf("Not enough!\n");
} else if (limit == ws_counter) {
printf("OK!\n");
}
}; // ⑭
```
1. Во-первых, если используем параметризуемые макросы, вход надо оборачивать в скобки.
Во-вторых, `;` в конце не нужна и всё портит.
В-третьих, не `<`, а `>`.
В-четвёртых, от макроса вообще избавиться можно было. Но если оставить, то такой:
```c
#define MAX(a, b) ((a) > (b) ? (a) : (b))
```
2. `int main()` всё же. `int main()` и `int main(int argc, char *argv[])` стандарт разрешает, а остальное — implementation-defined, то есть бяка.
3. Инициализация переменных забыта. В переменных мусор! Оптимизатор вообще считать будет это неинициализированной памятью и может подсунуть туда что угодно. При этом оптимизировать все использования этой переменной он не будет, а мог бы!
4. Не тот приоритет у `=`, чтоб без скобок брать. Надо:
```c
while ((c = getchar()) != EOF) {
```
А ещё `c` типа `char`, а должен быть `int`, потому что `EOF` в `char` не влезает.
5. Если уж и использовать инкремент, то постфиксный: `i++`.
6. `realloc(str, i + 1)`. Плюс нет обработки ошибок.
7. Строка не терминирована NUL-байтом. Надо:
```c
str[i] = '\0';
```
8. Пихать в строку формата `printf` недоверенный инпут — верный способ стать инвалидом и внести в программу уязвимость. Надо:
```c
printf("%s", str);
```
9. У нас уже есть длина, кровью и потом посчитанная, — `i`. Зачем перезатирать переменную? Зачем `strlen` использовать? А вы в курсе, что я мог в инпут спокойно и `\0` кинуть, например? У вас в памяти 2 гига для строки выделено, а юзать будете только первые 10 байт. Потрясающе же. 👏
К слову, `;` там лишний.
10. Раз уж барин не соизволил проверять `scanf`, можно было хотя бы сюда `= 0` вписать.
11. `printf`
12. Нет обработки ошибок. Что будет, если:
- я не дам вообще ничего?
- я дам не число?
- я дам число и мусор?
- я дам слишком большое число?
А ещё `&limit`.
13. А если `ws_counter == limit`? Третью ветку первой надо ставить было. А лучше вообще сравнения.
14. `;` не нужна.