更改指针时C ++指针中断 更深入的分析解决方案

如何解决更改指针时C ++指针中断 更深入的分析解决方案

当我在Union中更改指针时,其他指针会断开并显示无效的指针。

CustomDataTypeExample类:

struct CustomDataTypeExample {
float x;
float y;
float z;
CustomDataTypeExample() = default;
CustomDataTypeExample(float x,float y,float z) {

    this->x = x;
    this->y = y;
    this->z = z;

};

// ...
};

ConfigCustomDataTypeExample类:

struct ConfigCustomDataTypeExample {
public:
    ConfigCustomDataTypeExample() = default;
    ConfigCustomDataTypeExample(CustomDataTypeExample values) {
        x = &values.x;
        y = &values.y;
        z = &values.z;
    }
    union {
        struct {

            CustomDataTypeExample* ex;
        };
        struct {

            float* x;
            float* y;
            float* z;
        };
    };
};

主要:

ConfigCustomDataTypeExample example({ 1.2f,3.4f,5.6f });
float value = 565;
example.x = &value;
std::cout << example.ex->x << "," << example.ex->y << "," << example.ex->z << "\n";
std::cout << *example.x << "," << *example.y << "," << *example.z << "\n";

输出:

565,-1.07374e+08,-1.07374e+08
565,3.4,5.6

到底发生了什么?如果我不将example.x更改为指向其他内容,它将正常工作,否则,如果我更改它,则将破坏其他指针。

解决方法

TL; DR::三种不同的未定义行为:生命周期问题,访问联合的非活动成员(无非标准扩展名)以及通过以下成员取消引用无效的指针值example.ex(对声明的工会代表的误解)。

看起来像您可以使用普通引用一样。完整的解决方案在最后进行描述。

更深入的分析

这实际上是一个非常有趣的问题,因为这里发生了很多事情!三种不同的不确定行为。让我们逐一检查这些内容。

首先,如注释中所述,您正在将参数values的地址分配给xyz(成员的地址)。参数values具有自动存储持续时间,这意味着它在ConfigCustomDataTypeExample的构造函数最后被破坏。

struct ConfigCustomDataTypeExample {
public:
    ConfigCustomDataTypeExample() = default;
    ConfigCustomDataTypeExample(CustomDataTypeExample values) {
        x = &values.x;
        y = &values.y;
        z = &values.z;
    } // Pass this line x,y and z store invalid pointer values
      // (addresses to now destructed members of values).
      // Any indirection through these pointers is undefined behavior.
...

使用程序,您仍然可以读取yz的值。这是不确定行为的本质:您有时可能会获得明智的结果,但不能保证。例如,当我尝试运行您的程序时,yz的结果截然不同。这是第一个清除的UB。接下来,让我们检查一下联合声明,以了解它的真正含义。

类是由一系列成员组成的类型。联合是一种特殊类型的类,一次最多可以容纳其一个非静态数据成员。工会当前持有的对象称为活动成员。这意味着一个联合仅与其最大的数据成员一样大,如果担心内存使用情况,这将很有用。

union {
  struct {
      CustomDataTypeExample* ex;
  };
  struct {
      float* x;
      float* y;
      float* z;
  };
};

对于该联合,成员是两个匿名结构(请注意,C ++标准禁止使用匿名结构)。联合的大小取决于最大的结构,即float*结构。对于64位系统,指针类型的大小通常为8个字节,因此对于64位系统,此联合的大小为24个字节。

使用联合会带来什么,您显然不是在利用联合来减少内存消耗。相反,您正在尝试执行称为 type punning 的操作。类型校正是当您尝试将一种类型的二进制表示形式解释为另一种类型时。根据C ++标准类型,使用联合修剪是未定义的行为(第二),尽管许多编译器提供了允许此操作的非标准扩展。让我们根据标准规则来分析您的main程序:

ConfigCustomDataTypeExample example({1.2f,3.4f,5.6f});
// The anonymous struct holding 3 float* is now the active member.
// Though,all of the pointers are invalid,as already mentioned.

float value = 565;

example.x = &value;
// example.x is now a valid ptr value
 
std::cout 
    << example.ex->x << ","  // UB: Accessing a non-active member
    << example.ex->y << ","  // UB: non-active and invalid ptr (more on that later)
    << example.ex->z << "\n"; // UB: same as above

std::cout 
    << *example.x << ","     // This is ok (active member and valid ptr)
    << *example.y << ","     // UB: indirection to an invalid ptr
    << *example.z << "\n";    // UB: same as above

再次,取消引用565时,未定义的行为足以打印example.ex->x。这是因为float* xexample.ex->x在联合的二进制表示中重叠,尽管这仍然是未定义的行为。

我们首先通过更改ConfigCustomDataTypeExample以将引用作为参数ConfigCustomDataTypeExample(CustomDataTypeExample& values)并在main中声明一个CustomDataTypeExample变量来快速解决生命周期问题。 我还使用gcc进行编译,其中明确定义了带有联合的类型punning(非标准扩展名):

CustomDataTypeExample data{1.0f,2.0f,3.0f};
ConfigCustomDataTypeExample example(data);
    
float value = 565;
example.x = &value;

std::cout 
    << example.ex->x << ","  // This is now ok (using gcc's non-standard extension)
    << example.ex->y << ","  // Something seems odd
    << example.ex->z << "\n"; // with these two lines
    
std::cout 
    << *example.x << ","     // Now well defined
    << *example.y << ","     // same
    << *example.z << "\n";    // same

这里什么也没有。我的一次运行的输出是:

565,1961.14,4.59163e-41
565,2,3

好的,至少现在xyz值是有效的,但是当取消引用example.ex的部分时,我们仍然会得到垃圾值。是什么赋予了?让我们回到我们的联合声明,并思考它如何转换为二进制表示。这是一个粗略的图:

[float* x,float* y,float* z]

因此我们联合的内存布局是三个浮点指针,每个浮点指针都指向一个浮点值(相当于存储三个浮点指针的数组,例如float* arr[3])。 但是,我们正在尝试使用example.exfloat* x解释为由3个浮点组成的数组。这是因为CustomDataTypeExample的内存布局等效于包含3个浮点值的数组,而尝试引用其成员等效于数组索引。

我认为gcc的扩展名是基于C90标准第6.5.2.2条脚注82对example->ex的解释:

如果用于访问并集对象内容的成员与上次用于在对象中存储值的成员不同,则该值的对象表示形式的适当部分将重新解释为新类型中的对象表示形式,如下所示: 6.2.6中描述的过程(有时称为“类型校正”的过程)。这可能是陷阱的表示。

我们还可以通过查看编译器如何将这三行转换为汇编来验证这一点:

example.x = &value;

std::cout 
    << example.ex->x << "," 
    << example.ex->y << "," 
    << example.ex->z << "\n";

使用godbolt,我们得到以下信息(我只参加了相关的部分):

// Copies the value of rax to the memory pointed by QWORD PTR [rbp-48]
mov     QWORD PTR [rbp-48],rax  // example.x = &value;

// Copy a 32-bit value from memory address rax to eax.
// (eax register is used here to pass the value to std::cout)
// No surprises yet,as this address has a well defined floating point value (526).
mov     eax,DWORD PTR [rax]     // example.ex->x

// Not good,tries to copy a floating point value from memory address 
// [rax + 4 bytes]. Equivalent to *(&value + 1). This is gonna get 
// whatever random junk is in that part of memory.
mov     eax,DWORD PTR [rax+4]   // example.ex->y

我们可以很清楚地看到编译器如何尝试将example.ex 指向的地址解释为包含3个浮点值的内存区域,即使该区域仅包含一个浮点值。因此,第一次读取就可以了,但是第二次和第三次取消引用就非常错误。

这段代码产生的汇编非常相似,这并不奇怪,因为行为是相同的:

float* value_ptr = &value;

std::cout
    << *value_ptr << ","    // equivalent to example.ex->x,OK
    << value_ptr[1] << ","  // equivalent to example.ex->y,plain UB
    << value_ptr[2] << '\n'; // equivalent to example.ex->z,plain UB

这是未定义行为的情况,与第一种情况非常相似。程序正在通过无效的指针值( third )执行间接操作。

这三个未定义的行为组合在一起,导致在执行main时出现怪异的值。现在在解决方案上。

解决方案

首先,让我们消除小问题。 CustomDataTypeExample显然是一个聚合,仅将数据封装在其中,因此无需显式声明特殊的成员函数(在这种情况下为构造函数)。特殊成员函数是隐式声明的(并且是微不足道的):

struct CustomDataTypeExample {
    float x;
    float y;
    float z;
};

// Construct an instance of CustomDataTypeExample by aggregate initializing.
// This was also utilized earlier.
CustomDataTypeExample data{1.0f,3.0f};

该解决方案的作用,似乎是您在尝试为一个简单问题提供额外的抽象层。普通引用应该可以解决问题。您可能已经注意到,没有那么复杂的联合设置,这很容易出错。在C ++中,仅应将联合真正用于减少内存不足的系统上的内存消耗。

因此,我将摆脱ConfigCustomDataTypeExample并利用如下引用:

CustomDataTypeExample data{1.0f,3.0f};
CustomDataTypeExample& data_ref = data;

// Modifies the contents of the existing data
data_ref.x = 565;

std::cout 
    << data_ref.x << "," 
    << data_ref.y << "," 
    << data_ref.z << '\n';

在使用具有自动存储持续时间的变量时,可以使用引用。与指针相比,带有引用的生存期问题更难创建,并且总体解决方案通常更简单。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-