Массив символов имеет другой адрес при копировании указателем

Я пытаюсь обмануть константность переменных-членов класса. Итак, у меня есть следующий код:

"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;
}

Хотите **(char **) pointer = 'b';Демо

Jarod42 03.07.2024 11:24
pointer — это (кроме преобразований в void *) адрес sv, который является объектом типа StringView и отличается от str. Отсюда и проблема. Вам нужен (эффективный) доступ к значению sv._begin, что (поскольку StringView является тривиальным типом) можно сделать через **(char **)pointer = 'b'. (char **)pointer получает адрес sv._begin (т. е. &sv._begin), поэтому его необходимо разыменовать дважды (один раз, чтобы добраться до sv._begin, и один раз, чтобы получить то, на что указывает sv._begin.
Peter 03.07.2024 11:50
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В вашем случае первый член действительно имеет тот же адрес, что и структура, поэтому

&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)

Это исправляет ситуацию, спасибо! Но как это работает? Потому что адрес указателя по-прежнему неправильный, но двойное разыменование меняет данные?

ashamedgap 03.07.2024 11:38

Если вы понимаете &sv == &sv._begin, просто используйте правильный тип, т. е. decltype(&sv.begin) — это char const **. Вы также можете попробовать использовать простую структуру: struct S {int n;} s;, затем *(int*) &s = 42;.

Jarod42 03.07.2024 11:48

о да, спасибо! Да, это двойной указатель, поскольку это адрес структуры, а затем адрес строки внутри, спасибо!

ashamedgap 03.07.2024 11:50

Другие вопросы по теме