Переосмысление указателей

189
27 марта 2018, 01:41

Я часто пишу разную ерунду для эксперимента (чтоб получше понять что к чему). Вот одна из них:

#include <iostream>
#include <string>
using  std::string;
struct Test {
    char* s;
    Test(const string str)
        : s(new char[strlen(str.c_str()) + 1])
    { strcpy(s, str.c_str()); }
};
int main()
{      
    const string ms = "string"; 
    Test t(ms);  
    char* p = t.s; 
    //преобразование указателья на содержащий указатель на строку "string"?
    char *pt = reinterpret_cast<char*>(&t);     
    for (size_t i = 0; i < ms.size(); ++i) {
        *pt = 'x' + i;   // UB?
        std::cout << t.s << endl;
    }
    return 0;
}
 /*результат:
    string
    tring
    ring
    ing
    ng
    g*/

Хотелось бы получше понять что тут происходит. Хотелось бы услышать ответ подробнее(насколько это возможно). Извеняюсь: добавлю к вопросу почему указатели p и pt указывают на разную сущность?

Answer 1

У вас есть структура Test, хранящая в себе указатель s типа char *. В строке

Test t(ms);

конструируется объект t типа Test. Судя по конструктору, указатель t.s инициализируется валидным значением. Теперь t.s указывает на первый элемент массива char'ов, который оканчивается нулевым символом, т.е. t.s указывает на первый символ некоторой строки.

Затем, вы получаете адрес объекта t и преобразуете его к указателю на char:

char *pt = reinterpret_cast<char*>(&t);

Таким образом, pt хранит адрес первого байта объекта t, но не строки, на которую указывает указатель t.s!.

Следующим шагом, вы начинаете изменять объект t через указатель на его первый байт:

*pt = 'x' + i;

Повторюсь ещё раз: вы изменяете не строку, адрес первого символа которой хранится в указателе t.s, который в свою очередь является подобъектом t, вы изменяете непосредственно первый байт объекта t.

Не то что бы так делать нельзя, но в данном конкретном случае, результат может быть непредсказуемым. Немного модифицируем тело функции main, чтобы посмотреть как изменяется t.s после модификации первого байта объекта t:

const string ms = "st"; 
Test t(ms);  
char* p = t.s; 
char *pt = reinterpret_cast<char*>(&t);     
//Выводим адреса символов строки, на которую указывает  t.s
for (size_t i = 0; i < ms.size(); ++i)
    cout << "original = " << uintptr_t(t.s + i) << endl;
for (size_t i = 0; i < ms.size(); ++i) {
    *pt = 'x' + i;   // UB?
    //Выводим модифицированный t.s
    cout << "modified = " << uintptr_t(t.s) << endl;
    //std::cout << t.s << endl;
}

Один из возможных выводов программы выглядит так:

original = 94862834732064  
original = 94862834732065  
modified = 94862834732152  
modified = 94862834732153

Видно, что t.s после модификации указывает за пределы строки, на которую он указывал изначально. t.s — невалидный указатель, попытка его разыменования приводит к неопределённому поведению.

Если очень сильно повезёт, то первый байт объекта t будет равен значению символа 'x' и программа отработает именно так как в вашем вопросе, но это просто совпадение.

Answer 2

*pt = 'x' + i; будет изменять первый октет указателя t.s делая его невалидным. Последующие попытки разыменовать его приводят к неопределенному поведению.

READ ALSO
Ошибка: During startup program exited with code 0xc0000135 при вызове функции с Opencv

Ошибка: During startup program exited with code 0xc0000135 при вызове функции с Opencv

Я переустанавливал WIndows, qt у меня находиться тут:

182
Работа с Русским языком в Visual Studio C++

Работа с Русским языком в Visual Studio C++

Нашёл, как мне показалось, очень полезную статью: ссылка

175
Проблемы с подключением bzip2 к проекту Qt/C++

Проблемы с подключением bzip2 к проекту Qt/C++

Пытаюсь подключить стороннюю библиотекуСобираю библиотеку, генерируются следующие файлы: Подключаю библиотеку в QtCreator'е:

236
Как вывести названия всех файлов в определенной папке. xcode c++

Как вывести названия всех файлов в определенной папке. xcode c++

Имеется папка с наличием в ней текстовых документов например: atxt, b

189