Я пытаюсь обмануть константность переменных-членов класса. Итак, у меня есть следующий код:
"StringView.хх":
#pragma once
#include <cstring>
class StringView {
public:
const char* _begin = nullptr;
const char* _end = nullptr;
explicit StringView(const char* str) : _begin(str), _end(str + strlen(str)) {}
};
основной.cpp:
#include <iostream>
#include "StringView.hh"
int main() {
char str[] = "cat";
StringView sv(str);
std::cout << str << '\n';
void *pointer = reinterpret_cast<void *>(&sv);
// *(char *) pointer = 'b'; // I would also like to set it to a whole string, but that doesn't work, it would work if it was std::string, not char *, maybe char ** would work?
// std::cout << str << '\n';
std::cout << "Pointer to StringView: " << pointer << "\n";
std::cout << "String: " << (void *)str << "\n";
std::cout << "String from StringView _begin: " << (void *)sv._begin << "\n";
return 0;
}
Результат:
cat
Pointer to StringView: 0xe25bffde0
String: 0xe25bffdf4
String from StringView _begin: 0xe25bffdf4
Таким образом, строка и строка из StringView имеют одинаковый адрес. Это происходит как в cmake, так и в gcc, однако указатель в main.cpp этого не делает. Почему это?
Когда я раскомментирую строки кода, я получаю это:
cat
cat
Pointer to StringView: 0x81491ffc50
String: 0x81491ffc64
String from StringView _begin: 0x81491ffc62
Теперь у всех троих разные адреса. Почему это? Могу ли я как-нибудь обмануть это, чтобы пройти квалификатор const без использования очевидного const_cast<>()
? Это также не работает в c (использование структуры, явное удаление, использование printf и т. д.).
Я знаю, что константные строки, которых нет в объекте (например, StringView sv), хранятся в некоторой постоянной памяти, отличной от памяти переменных. Это похоже на пространство глобальных переменных/статических переменных, поэтому я сделал str не const char[], и он все еще находится в какой-то другой памяти, которая меняется в зависимости от того, меняю ли я эту память и другие вещи. Кроме того, я не использую std::string, потому что у него есть перегруженный оператор =, и это нарушает константность, потому что в этом случае он другой.
Также по умолчанию _begin и _end должны быть частными, я просто сделал их общедоступными, чтобы проверить адреса.
Код в С:
#include <stdio.h>
#include <string.h>
struct StringView {
const char* _begin;
const char* _end;
};
struct StringView create(const char *str) {
struct StringView a = {str, str + strlen(str)};
return a;
}
int main() {
char str[] = "cat";
struct StringView sv = create(str);
printf("%s\n", str);
void *pointer = (void *) &sv;
*(char *) pointer = 'b';
printf("%s\n", str);
printf("Pointer to StringView: %p\n", pointer);
printf("String: %p\n", (void *)str);
printf("String from StringView _begin: %p\n", (void *)sv._begin);
return 0;
}
pointer
— это (кроме преобразований в void *
) адрес sv
, который является объектом типа StringView
и отличается от str
. Отсюда и проблема. Вам нужен (эффективный) доступ к значению sv._begin
, что (поскольку StringView
является тривиальным типом) можно сделать через **(char **)pointer = 'b'
. (char **)pointer
получает адрес sv._begin
(т. е. &sv._begin
), поэтому его необходимо разыменовать дважды (один раз, чтобы добраться до sv._begin
, и один раз, чтобы получить то, на что указывает sv._begin
.
В вашем случае первый член действительно имеет тот же адрес, что и структура, поэтому
&sv == &sv._begin
Вы ввели неправильный тип, так и должно быть
void *pointer = (void *) &sv;
**(char **) pointer = 'b'; // *const_cast<char*>(sv._begin) = 'b'
Вы фактически меняете значение указателя, что-то вроде (в зависимости от порядка байтов)
sv._begin = (sv._begin & 0xFFFFFF00) | 'b'; // or (sv._begin & 0x00FFFFFF) | ('b' << 24)
Это исправляет ситуацию, спасибо! Но как это работает? Потому что адрес указателя по-прежнему неправильный, но двойное разыменование меняет данные?
Если вы понимаете &sv == &sv._begin
, просто используйте правильный тип, т. е. decltype(&sv.begin)
— это char const **
. Вы также можете попробовать использовать простую структуру: struct S {int n;} s;
, затем *(int*) &s = 42;
.
о да, спасибо! Да, это двойной указатель, поскольку это адрес структуры, а затем адрес строки внутри, спасибо!
Хотите
**(char **) pointer = 'b';
Демо