8.9 KiB
Задания
Вопрос
Скомпилируйте следующий файл и посмотрите содержимое секции .data с помощью objdump. По какому адресу начинается переменная x? Попробуйте убрать _Alignas(128) и объясните эффект.
$ clang alignas.c -c -o alignas.o
$ clang alignas.o -o alignas
$ objdump -D alignas.o
С _Alignas(128):
0000000000004080 <x>:
4080: ff (bad)
4081: ff (bad)
4082: ff (bad)
4083: ff (bad)
4084: ff (bad)
4085: ff (bad)
4086: ff (bad)
4087: ff .byte 0xff
Без _Alignas(128):
0000000000004010 <x>:
4010: ff (bad)
4011: ff (bad)
4012: ff (bad)
4013: ff (bad)
4014: ff (bad)
4015: ff (bad)
4016: ff (bad)
4017: ff .byte 0xff
Изменился адрес начала переменной x. С _Alignas(128) он выравнивается по 128 байт, т.е начинается не с 4010, а с 4080.
Подробнее про команду _Alignas(SIZE) можно прочитать тут.
В целом команда делает так, чтобы любые выделяемые данные выделялись с адресов, кратных аргументу, то есть вы можете выделить данные по адресу 10000 в 1 байт, но при минимальной кратности в 128 следующие данные поместятся не в 10001 а в 10080 (адреса в hex).
Вопрос
Напишите минимальный пример с таким кодом, скомпилируйте его со строгим соответствием стандарту C17 (-std=c17 -pedantic -Wall). Что выведет компилятор? Объясните сообщение.
int main() {
int x = -1;
int y = 2;
if (x > 0)
if (y > 0) {
return 1;
}
else {
return 0;
}
}
Мы получим предупреждение:
ub.c: В функции «main»:
ub.c:5:8: предупреждение: используйте фигурные скобки во избежание неоднозначной трактовки «else» [-Wdangling-else]
5 | if (x > 0)
| ^
Компилятор понял, что возможна неоднозначная трактовка и вывел предупреждение. Если использовать фигурные скобки, предупреждение не возникнет.
Вопрос
Найдите в AST для примера ниже имя класса. Какие поля хранятся в узле этого дерева? Как вы думаете, зачем?
public class Program {
public static void main(String[] args) {
int x = 100;
if (x > 0) {
System.out.println("Greater than zero");
}
else {
System.out.println("Less than or equal to zero");
}
}
}
typeIdentifier {
name: "typeIdentifier"
children: {
Identifier: [
{
image: "Program"
startOffset: 13
endOffset: 19
startLine: 1
endLine 1
startColumn: 14
endColumn: 20
tokenTypeIdx: 3
}
]
}
location: {
startOffset: 13
endOffset: 19
startLine: 1
endLine 1
startColumn: 14
endColumn: 20
}
}
Хранятся поля типа токена и локация в тексте кода программы, чтоб использовать их при дальнейшей компиляции из AST в байткод.
Задание 0.5
Найдите в AST для примера выше конструкцию if. Добавьте код так, чтобы повторить ситуацию с вложенным в if предложением if-else из первого примера. Нарисуйте примерный вид AST для него.
Вопрос
Что выведется в stdout? Если это неожиданный результат, как переписать dbl чтобы избавиться от нежелательного и неожиданного поведения?
#define dbl(y) y * 2
...
printf("%d", dbl(3+3));
В stdout выведется 9, а не 12, так как препроцессор заменит код на
printf("%d", 3+3 * 2);
и из порядка операций это равняется 9.
Чтобы избавиться от нежелательного и неожиданного поведения, стоит в макросе обернуть yв скобки:
#define dbl(y) (y) * 2
Задание 1
Напишите программу которая будет использовать этот макрос для вывода переменной, числа и константы, заданной с использованием макроса. Протестируйте её с помощью ключа -E для gcc/clang.
#define print_var(x) printf(#x " is %d", x )
Вопрос
Что будет если написать print_var(42)?
Будет выведено 42 is 42.
Вопрос
Протестируйте эту программу с помощью ключа -E для gcc/clang.
int main() {
int64_t x = 42;
double d = 99.99;
int64_t_print(x);
newline_print("");
double_print(d);
newline_print("");
return 0;
}
Вопрос
Протестируйте эту программу с помощью ключа -E для gcc/clang. Что раскрывается раньше: _Generic или #define?
int main() {
int64_t x = 42;
double d = 99.99;
_Generic((x), int64_t : int64_t_print(x), double : double_print(x), default : error("Unsupported operation"));
newline_print();
_Generic((d), int64_t : int64_t_print(d), double : double_print(d), default : error("Unsupported operation"));
newline_print();
return 0;
}
#define раскрывается раньше, т.к после препроцессинга (gcc -E) мы видим, что подстановка _Generic выполнена не была.
Задание 2
Напишите функции добавления элемента в конец обобщённого списка и вывода обобщённого списка на экран используя макросы: функции list_##type##_push и list_##type##_print внутри макроса DEFINE_LIST. Создайте и выведите с их помощью три любых списка разной длины.
Задание 3
Напишите функции добавления элемента в конец обобщённого списка и вывода обобщённого списка на экран используя generic: макросы list_push и list_print, которые используют _Generic. Создайте и выведите с их помощью три любых списка разной длины.
Задание 4
Изучим простой принтер для AST-дерева. Дерево будем задавать прямо в коде с помощью небольшого доменно-специфичного языка (DSL), который определим прямо в программе.
Допишите в main код, чтобы вывести следующие выражения (можно со скобками):
999 + 728
4+2*9
Расширьте этот пример, добавив тип вершин AST для деления и принтер для него. Выведите следующее выражение:
(3+5) * (9 / 7)