-
Notifications
You must be signed in to change notification settings - Fork 19
[DRAFT] C++. Глава 16.2. Адресная арифметика #321
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
0fd958f to
adbf413
Compare
05405c0 to
259a1f8
Compare
c5507e4 to
054b000
Compare
2db09dd to
81d8842
Compare
| ```cpp | ||
| int ret_code = -5; | ||
| bool retry = false; | ||
| bool * p = &retry; | ||
| ``` | ||
|
|
||
|  {.illustration} | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Хотелось бы, чтобы читатель стал понимать как это всё устроено. Для этого предлагаю показать пример, как и можно посмотреть используемую переменными память:
import std;
template<class T>
void show_used_memory(const T* ptr, std::string_view name)
{
const std::intptr_t address = reinterpret_cast<const std::intptr_t>(ptr);
std::println("\n{}:", name);
std::print("|");
for (auto i = 0; i < sizeof(T); ++i)
std::print("{:x}|", address + i);
std::println("");
}
int main()
{
int ret_code = -5;
bool retry = false;
bool * p = &retry;
show_used_memory(&retry, "retry");
show_used_memory(&ret_code, "ret_code");
show_used_memory(&p, "p");
}Возможный вывод:
retry:
|7ffdb51deffb|
ret_code:
|7ffdb51deffc|7ffdb51deffd|7ffdb51deffe|7ffdb51defff|
p:
|7ffdb51df000|7ffdb51df001|7ffdb51df002|7ffdb51df003|7ffdb51df004|7ffdb51df005|7ffdb51df006|7ffdb51df007|
Также предлагаю скорректировать картинку. Убрать пустое пространство между ret_code и p. Адреса ret_code и p должны быть кратны 8. Можно использовать адреса из примера, попался удачныф переход между ret_code и p.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Лучше в колонку адраеса выводить:
import std;
template<class T>
void show_used_memory(const T* ptr, std::string_view name)
{
const std::intptr_t address = reinterpret_cast<const std::intptr_t>(ptr);
std::println("\n|{:<10}|{:x}|", name, address);
for (auto i = 1; i < sizeof(T); ++i)
std::println("|{:<10}|{:x}|", ' ', address + i);
}
int main()
{
int ret_code = -5;
bool retry = false;
bool * p = &retry;
show_used_memory(&retry, "retry");
show_used_memory(&ret_code, "ret_code");
show_used_memory(&p, "p");
}Так нагляднее:
|retry |7ffe3bc948eb|
|ret_code |7ffe3bc948ec|
| |7ffe3bc948ed|
| |7ffe3bc948ee|
| |7ffe3bc948ef|
|p |7ffe3bc948f0|
| |7ffe3bc948f1|
| |7ffe3bc948f2|
| |7ffe3bc948f3|
| |7ffe3bc948f4|
| |7ffe3bc948f5|
| |7ffe3bc948f6|
| |7ffe3bc948f7|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Для этого предлагаю показать пример,
Добавила пример. Но не сюда, а в секцию про прибавление целого к указателю. Иначе будет непонятно, как этот пример кода работает.
Также предлагаю скорректировать картинку.
Готово.
cpp/cpp_chapter_0162/text.md
Outdated
|
|
||
| Разберем подробнее каждую из арифметических операций. Удобнее всего это делать на примере указателей на элементы сишных массивов. Заодно узнаем, что такое сишные строки. | ||
|
|
||
| ## Сишные массивы |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Не стоит использовать такое наименование, лучше массивы фиксированного размера. Можно в тексте упомянуть, что такие массивы в обиходе называют сишными.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Переименовала название секции.
p.s. Мы уже писали про массивы фиксированного размера здесь.
| ```cpp | ||
| std::println("Size of int: {} bytes", sizeof(int)); | ||
|
|
||
| const std::size_t n = 5; | ||
| int pow_series[n] = {16, 32, 64, 128, 256}; | ||
|
|
||
| std::println("\nArray of ints:"); | ||
|
|
||
| for (std::size_t i = 0; i < n; ++i) | ||
| { | ||
| std::println("Address: {}. pow_series[{}]={}", | ||
| static_cast<void *>(&pow_series[i]), i, pow_series[i]); | ||
| } | ||
| ``` | ||
| ``` | ||
| Size of int: 4 bytes | ||
|
|
||
| Array of ints: | ||
| Address: 0x7ffd9239f430. pow_series[0]=16 | ||
| Address: 0x7ffd9239f434. pow_series[1]=32 | ||
| Address: 0x7ffd9239f438. pow_series[2]=64 | ||
| Address: 0x7ffd9239f43c. pow_series[3]=128 | ||
| Address: 0x7ffd9239f440. pow_series[4]=256 | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можно использовать std::array<>, это более в духе современного C++:
import std;
int main()
{
std::println("Size of int: {} bytes", sizeof(int));
std::array<int, 5> pow_series = {16, 32, 64, 128, 256};
std::println("\nArray of ints:");
std::string line(30, '-');
std::println("{}", line);
std::println("{:>2} {:>16} {:>9}", 'i', "address", "value");
std::println("{}", line);
for (std::size_t i = 0; i < pow_series.size(); ++i)
{
std::println("{:>2} {:>16} {:>9}",
i, static_cast<void *>(&pow_series[i]), pow_series[i]);
}
std::println("{}", line);
}Предлагаю использовать другое представление, в виде таблицы:
Size of int: 4 bytes
Array of ints:
------------------------------
i address value
------------------------------
0 0x7ffc79eadfa0 16
1 0x7ffc79eadfa4 32
2 0x7ffc79eadfa8 64
3 0x7ffc79eadfac 128
4 0x7ffc79eadfb0 256
------------------------------
Использование std::array и табличное представление на твоё усмотрение.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Готово.
cpp/cpp_chapter_0162/text.md
Outdated
| 9621534751069176051 2054564862222048242 | ||
| ``` | ||
|
|
||
| ## Сишные строки |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Нуль-терминированная строка такое название следует использовать название в заголовке. Также стоит дать ссылку на вики:
https://ru.wikipedia.org/wiki/%D0%9D%D1%83%D0%BB%D1%8C-%D1%82%D0%B5%D1%80%D0%BC%D0%B8%D0%BD%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%BE%D0%BA%D0%B0
Далее можно сказать, что в обиходе их часто называют сишными строками. И в отличии от массивов это действительно часто встречаемое сленговое слово (потому что эти строки используются повсеместно).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Готово.
| Для нулевого элемента вместо этой записи можно использовать более лаконичную: | ||
|
|
||
| ```cpp | ||
| int * p = arr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Да, для std::array<> такое не прокатит. Либо &arr[0] либо arr.begin(). Это аргумент не использовать std::array. Но в любом случае стоит напомнить читателю об std::array<> и сказать, что он аналогичен C-подобному массиву, но не требует держать отдельно размер массива и передавать его повсюду.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Привела примеры и с array, и с сишным массивом.
cpp/cpp_chapter_0162/text.md
Outdated
|
|
||
| Но [какой тип](https://en.cppreference.com/w/cpp/language/string_literal.html) у самого литерала? Перед вами так называемая сишная строка, а по сути — сишный массив символов. У него тип `const char[n]`, где `n` — длина литерала + 1. Например, у литерала `"UART"` тип `const char[5]`. | ||
|
|
||
| Дополнительный элемент массива отводится под [завершающий ноль](https://en.wikipedia.org/wiki/Null_character) (terminating null character) — символ с кодом `U+0000`. Это маркер конца строки. Его не нужно добавлять к литералу вручную: компилятор сам запишет его в последний элемент. Завершающий ноль обозначается как `\0`: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
С кодом 0. U+0000 - UTF определение нулевого символа. Его использование может ввести в заблуждение, что в C++ с UTF всё хорошо. Поэтому лучше сразу сделать акцент на значение 0 и литерал \0. Опционально можно показать как определить нулевой литерал в UTF:
template <class T>
void print_char(T ch)
{
std::println("char size: {}, code: 0x{:x}", sizeof(ch), static_cast<std::size_t>(ch));
}
int main()
{
print_char('\u0000');
print_char(u8'\u0000');
print_char(u'\u0000');
print_char(U'\u0000');
}Вывод:
char size: 1, code: 0x0
char size: 1, code: 0x0
char size: 2, code: 0x0
char size: 4, code: 0x0
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Поправила. Про ютф не буду))
cpp/cpp_chapter_0162/text.md
Outdated
| char null_char = '\0'; | ||
| ``` | ||
|
|
||
| При передаче в функцию сишная строка приводится к указателю (array-to-pointer decay). Но в отличие от массива, она не требует передачи дополнительного параметра — длины. Не обязательно знать длину строки, чтобы избежать выхода за ее границы: достаточно помнить про завершающий символ `\0`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Стоит показать, что определение нуль терминированной строки равносильно определению массива символов, просто гораздо удобнее:
const char* c_str = "C-like string";
const char c_arr[] = {'C', '-', 'l', 'i', 'k', 'e', ' ', 's', 't', 'r', 'i', 'n', 'g', '\0'};
std::println("{}", c_str);
std::println("{}", c_arr);Отсюда следствие, что для определения строки без териминирующего 0, нужно определить её как массив символов.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Готово.
| }; | ||
|
|
||
| std::uint64_t * p = seed; | ||
| std::println("{} {}", *p, p[1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Нужно показать как вычислить размер C-подобного массива:
int arr[] = {10, 20, 0, -500, 8};
std::println("{}", sizeof(arr) / sizeof(*arr));5
Дополнительно в качестве задания можно спросить, какое значение будет напечатано, если будет ошибка компиляции или исполнения, то напечатать err:
int arr0[0] = {};
std::println("{}", sizeof(arr0) / sizeof(*arr0));0
Стоит пояснить, что ошибки и UB здесь нет так как подобные выражения вычисляются на этапе компиялции и sizeof(*arr0) не требует обращения к первому (несуществующему) элементу массива.
constexpr auto len = sizeof(arr0) / sizeof(*arr0);
std::println("{}", len);Кстати, в Си есть специальная техника по использованию массивов нулевой длины. Она позволяет хранить структуры и массив переменной длины одним блоком:
https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Стандарт C++ говорит, что заводить массив длины 0 незаконно. А эта штука - гцц расширение.
Про определение размера сишного размеры мы уже писали.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Вариант sizeof(arr) / sizeof(*arr) или sizeof(arr0) / sizeof(arr[0]) более 'каноничный', так как позволяет не указывать тип элемента прямо. В любом случае предлагаю ещё раз упомянуть этот момент там, где я предлагаю добавить про современный C++.
P.S. Кстати std::array<T, 0> может иметь размер 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Более каноничный способ добавила в главу 14.2. В эту главу добавила задачу "что выведет этот код" про длину со ссылкой на раздел 14.2 про определение длины.
cpp/cpp_chapter_0162/text.md
Outdated
| Когда к указателю прибавляется целое число, то хранящийся в нем адрес увеличивается на соответствующее значение, умноженное на размер типа данных. Если прибавить к указателю число, не превышающее длину массива, то он будет ссылаться на один из последующих элементов: | ||
|
|
||
| ```cpp | ||
| const std::size_t n = 4; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
n — не используется.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Убрала.
| Синтаксис языка позволяет применять к указателю оператор `[]`, чтобы через него обращаться к элементам массива: | ||
|
|
||
| ```cpp | ||
| std::uint64_t seed[] = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Тут стоит пояснить, что размер массива фиксированной длины можно опустить, если инициализации массива происходит при обявлении. Компилятор сам вычислит размер массива.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Про это было в 14.2. И ссылка на раздел про длину уже есть выше по этой главе.
81d8842 to
ea03e8f
Compare
| ```cpp | ||
| import std; | ||
|
|
||
| std::size_t size_ptr(int * buf) | ||
| { | ||
| return sizeof(buf); | ||
| } | ||
|
|
||
| std::size_t size_arr(int buf[]) | ||
| { | ||
| return sizeof(buf); | ||
| } | ||
|
|
||
| int main() | ||
| { | ||
| int raw_data[5] = {}; | ||
| std::size_t a = &raw_data[4] - raw_data; | ||
| std::size_t b = sizeof(raw_data); | ||
|
|
||
| std::println("{} {} {} {}", | ||
| a, | ||
| b, | ||
| b == size_ptr(raw_data), | ||
| b == size_arr(raw_data)); | ||
| } | ||
| ``` | ||
|
|
||
| ```consoleoutput {.task_source #cpp_chapter_0162_task_0050} | ||
| ``` | ||
| . {.task_hint} | ||
| ```cpp {.task_answer} | ||
| 4 20 false false | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это неудачная задача. Имеется две проблемы с функцией size_arr():
- Из-за неё не компилируется код в плейграунде.
- Название функции
size_arr()не соответствует тому, что она делает.
Ниже 4 разных варианта на замену size_arr(), в зависимости от того, что требуется:
import std;
std::size_t size_ptr(int * buf)
{
return sizeof(buf);
}
// вместо size_arr [1]
template<class A>
std::size_t size_ptr2(A _unused)
{
return sizeof(A);
}
// вместо size_arr [2]
template<class Item, std::size_t Count>
std::size_t bytes_count(const Item (&_unused)[Count])
{
return sizeof(int[Count]);
}
// вместо size_arr [3]
template<class Item, std::size_t Count>
std::size_t array_size(const Item (&_unused)[Count])
{
return Count;
}
// вместо size_arr [4]
// std::size()
int main()
{
const std::size_t sz = 5;
int raw_data[sz] = {};
//std::size_t items = &raw_data[sz - 1] - raw_data;
std::size_t items = std::end(raw_data) - std::begin(raw_data);
std::size_t bytes = sizeof(raw_data);
std::println("{} {} \npointer size: {} \npointer size: {} \nbytes count: {} \narray size: {} \narray size: {}",
items,
bytes,
size_ptr(raw_data),
size_ptr2(raw_data),
bytes_count(raw_data),
array_size(raw_data),
std::size(raw_data)
);
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Просто убрала size_arr.
|
|
||
| ---------- | ||
|
|
||
| ## Резюме |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ИМХО, главу стоит расширить.
- Стоит рассказать как передавать Си-подобные массивы по ссылке:
import std;
template<class Item, std::size_t Count, class Filler>
void fill_array(Item (&arr)[Count], Filler filler)
{
for (std::size_t i = 0; i < Count; ++i)
arr[i] = filler(i);
}
template<class Item, std::size_t Count>
void print_array(const Item (&arr)[Count])
{
std::println("");
for (const Item *ptr = arr, *end = arr + Count; ptr < end; ++ptr)
std::print("{} ", *ptr);
}
char get_alphabet_char(std::size_t i)
{
static const char alphabet[] = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
const std::size_t pos = i % (sizeof(alphabet) / sizeof(*alphabet) - 1);
return alphabet[pos];
}
char get_alphabet_lower_case_char(std::size_t i)
{
return get_alphabet_char(2 * i + 1);
}
char get_alphabet_upper_case_char(std::size_t i)
{
return get_alphabet_char(2 * i);
}
int main()
{
const std::size_t alphabet_size = 26;
char alphabet[alphabet_size] = {};
fill_array(alphabet, get_alphabet_lower_case_char);
print_array(alphabet);
fill_array(alphabet, get_alphabet_upper_case_char);
print_array(alphabet);
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Как передача по ссылке связана с адресной арифметикой?
cpp/cpp_chapter_0162/text.md
Outdated
| ---------- | ||
|
|
||
| ## Резюме | ||
| - Сишная строка — это массив `char`, завершенный символом `\0`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Этот 'прекрасный' мир Си-легаси стоит разбавить современным C++:
import std;
int main()
{
int arr[] = {21, 8, 12, -4, 7, 7, 0};
char str[] = "Use modern C++!";
// вместо Си-шного метода вычисления размера использовать std::size()
std::size_t legacy_size = sizeof(arr) / sizeof(*arr);
std::size_t modern_size = std::size(arr);
std::println("{} {}", legacy_size, modern_size);
std::println("");
// классический обход по указателю:
for (const int *ptr = arr, *end = arr + legacy_size; ptr != end; ++ptr)
std::print("{} ", *ptr);
std::println("");
// можно заменить на итераторы, это полезно при написании обобщенного кода,
// когда требуется уметь работать с контейнерами разных типов
for (auto iter = std::begin(arr), end = std::end(arr); iter != end; iter = std::next(iter))
std::print("{} ", *iter);
std::println("");
// И конечно же для Си-шных строки и массивов доступен range-based for
for (char ch: str)
std::print("{} ", ch);
// Си-шные массивы можно превратить в std::array
// можно опустить тип элементов и размер массива
std::array modern_array = std::to_array(arr);
std::println("\n{}", modern_array);
// Си-шные строки можно превратить в std::string_view
std::string_view modern_str = std::string_view(str, std::size(str));
std::println("{}", modern_str);
}- Стоит сказать, что работа с указателями доступна также для стандартных контейнеров:
std::vector,std::array,std::string,std::string_viewиstd::span. Это может быть полезно для взаимодействия с легаси либо Си кодом:
import std;
#include <uchar.h>
// типа легаси код
long * fill(long *ptr, size_t count, long value)
{
for (long *end = ptr + count; ptr != end; ++ptr)
*ptr = value;
return ptr - count;
}
int main()
{
std::vector<long> buf(8, 0xbad);
std::span<long> slice{buf.begin() + 2, 4};
fill(slice.data(), slice.size(), 0xeffe);
for (long& it: buf)
std::print("{:X} ", it);
}Вывод:
BAD BAD EFFE EFFE EFFE EFFE BAD BAD
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Этот 'прекрасный' мир Си-легаси стоит разбавить современным C++
Про std::span, std::to_array и преимущества std::array мы рассказывали в 14.2. Это не относится к адресной арифметике. Если хочешь, могу тот раздел потом дополнить еще примером с итераторами, который ты написал.
Стоит сказать, что работа с указателями доступна также для стандартных контейнеров
Это будет в следующей подглаве.
Главу пока ревьюить рано! Реквест открыт для удобства @khva