Skip to content

Conversation

@Microvenator
Copy link
Contributor

@Microvenator Microvenator commented Jan 19, 2026

Главу пока ревьюить рано! Реквест открыт для удобства @khva

@Microvenator Microvenator changed the base branch from cpp-chapter-16 to main January 23, 2026 10:20
@Microvenator Microvenator changed the base branch from main to cpp-chapter-16 January 23, 2026 10:21
@Microvenator Microvenator changed the base branch from cpp-chapter-16 to main January 23, 2026 10:22
@Microvenator Microvenator force-pushed the cpp-chapter-16-2 branch 2 times, most recently from 2db09dd to 81d8842 Compare January 23, 2026 10:33
Comment on lines 11 to 18
```cpp
int ret_code = -5;
bool retry = false;
bool * p = &retry;
```

![Переменные в адресном пространстве](https://raw.githubusercontent.com/senjun-team/senjun-courses/refs/heads/cpp-chapter-16/illustrations/cpp/pointers_and_addresses.jpg) {.illustration}

Copy link
Collaborator

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.

Copy link
Collaborator

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|

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Для этого предлагаю показать пример,

Добавила пример. Но не сюда, а в секцию про прибавление целого к указателю. Иначе будет непонятно, как этот пример кода работает.

Также предлагаю скорректировать картинку.

Готово.


Разберем подробнее каждую из арифметических операций. Удобнее всего это делать на примере указателей на элементы сишных массивов. Заодно узнаем, что такое сишные строки.

## Сишные массивы
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Не стоит использовать такое наименование, лучше массивы фиксированного размера. Можно в тексте упомянуть, что такие массивы в обиходе называют сишными.

Copy link
Contributor Author

@Microvenator Microvenator Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Переименовала название секции.

p.s. Мы уже писали про массивы фиксированного размера здесь.

Comment on lines 41 to 64
```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
```
Copy link
Collaborator

@khva khva Jan 25, 2026

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 и табличное представление на твоё усмотрение.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Готово.

9621534751069176051 2054564862222048242
```

## Сишные строки
Copy link
Collaborator

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

Далее можно сказать, что в обиходе их часто называют сишными строками. И в отличии от массивов это действительно часто встречаемое сленговое слово (потому что эти строки используются повсеместно).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Готово.

Для нулевого элемента вместо этой записи можно использовать более лаконичную:

```cpp
int * p = arr;
Copy link
Collaborator

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-подобному массиву, но не требует держать отдельно размер массива и передавать его повсюду.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Привела примеры и с array, и с сишным массивом.


Но [какой тип](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`:
Copy link
Collaborator

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Поправила. Про ютф не буду))

char null_char = '\0';
```

При передаче в функцию сишная строка приводится к указателю (array-to-pointer decay). Но в отличие от массива, она не требует передачи дополнительного параметра — длины. Не обязательно знать длину строки, чтобы избежать выхода за ее границы: достаточно помнить про завершающий символ `\0`.
Copy link
Collaborator

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, нужно определить её как массив символов.

Copy link
Contributor Author

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]);
Copy link
Collaborator

@khva khva Jan 25, 2026

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Стандарт C++ говорит, что заводить массив длины 0 незаконно. А эта штука - гцц расширение.

Про определение размера сишного размеры мы уже писали.

Copy link
Collaborator

@khva khva Jan 27, 2026

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.

Copy link
Contributor Author

@Microvenator Microvenator Jan 29, 2026

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
const std::size_t n = 4;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n — не используется.

Copy link
Contributor Author

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[] = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут стоит пояснить, что размер массива фиксированной длины можно опустить, если инициализации массива происходит при обявлении. Компилятор сам вычислит размер массива.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Про это было в 14.2. И ссылка на раздел про длину уже есть выше по этой главе.

Comment on lines 389 to 421
```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
```
Copy link
Collaborator

@khva khva Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это неудачная задача. Имеется две проблемы с функцией size_arr():

  1. Из-за неё не компилируется код в плейграунде.
  2. Название функции 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)
        );
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Просто убрала size_arr.


----------

## Резюме
Copy link
Collaborator

@khva khva Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ИМХО, главу стоит расширить.

  1. Стоит рассказать как передавать Си-подобные массивы по ссылке:
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);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Как передача по ссылке связана с адресной арифметикой?

----------

## Резюме
- Сишная строка — это массив `char`, завершенный символом `\0`.
Copy link
Collaborator

@khva khva Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Этот 'прекрасный' мир Си-легаси стоит разбавить современным 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);
}
  1. Стоит сказать, что работа с указателями доступна также для стандартных контейнеров: 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

Copy link
Contributor Author

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. Это не относится к адресной арифметике. Если хочешь, могу тот раздел потом дополнить еще примером с итераторами, который ты написал.

Стоит сказать, что работа с указателями доступна также для стандартных контейнеров

Это будет в следующей подглаве.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants