一个编译问题

LangChain开发避坑指南:为什么Pylance总提示‘无法解析导入‘?实测7种修复方案 本文深入解析了在VS Code中使用LangChain开发时,Pylance频繁提示‘无法解析导入’的根本原因——Python解释器与语言服务器的环境路径不一致。文章提供了从基础排查到深度配置的7种实测修复方案,包括切换解释器、配置extraPaths、使用pyrightconfig.json等,旨在帮助开发者彻底解决此问题,提升AI应用开发效率。 阅读详情

感觉这篇文章很不错,就转贴过来了。

一个很巧妙的错误,欺骗编译器。

作者: Panic 2006年7月27日

很久没碰到什么有趣的问题了,今天看到一个,特写随笔一篇以作留念:P

问题的来源:

 一种让另外的类对本类成员只读,本类对本类成员可读写的方法。 [所有相关帖子]

例子
//a.h
class a
{
public:
  void test();
#ifndef A_CPP
const
#endif
   int b;
};

//a.cpp
#define A_CPP
#include "a.h"
#undef A_CPP

void a::test
{
   b = 12; //可以写
}

//b.cpp
#include "a.h"
void b()
{
  a _a;
   _a.b = 12;//编译错误 不可写
}
书童 xulingfv 发表于 2006-7-27 10:46:09


周星星的代码:

// a.hpp
struct foo
{
    foo();
#ifdef SOMETHING
    int a;
#endif
    int b;
};

// a.cpp
#define SOMETHING
#include "a.hpp"
#undef SOMETHING
foo::foo()
{
     a = 1;
     b = 2;
}

// main.cpp
#include "a.hpp"
#include <iostream>
int main()
{
    foo test;                         // 理论上讲,a.cpp和main.cpp中的foo定义不同,是不同的类型,所以应当报foo()未实现,但实际上却正确
    std::cout << test.b << std::endl; // 输出1
}


上面的代码,有兴趣的可以试试,这里只说明问题:

首先澄清几件事:

1,对编译器来说,一个函数定义在什么地方并不重要,无论foo::foo()定义在哪里,它都是foo类型的构造函数。

2,对链接器来说,区分不同函数(或者类型)的唯一方法是这个函数最后生成的符号名字,如果存在两个相同的符号,就会报重定义的错,而反之,无论这个符号来自何处,链接器都会把这个符号和对应的函数代码链接在一起。

3,对同一个编译单元,或者说,同一个.C/.CPP文件,里面的每个定义只会生成一份OBJ代码,模板则会为每个类型生成一份代码-如果可能的话。


这几件事情澄清之后,其实上面的代码就没有任何问题了,简单的说明一下:

第一段代码:

a.cpp和b.cpp中的class a,毫无疑问并非同一个类型,但是利用#define和#undefine,强制生成了完全相同的类型名字。

为了区分,这里把a.cpp中的class a称作a1,b.cpp中的称作a2。

当编译a.cpp的时候,编译器使用的是含有int b的定义,这时候void a::test是void a1::test。

而编译b.cpp的时候使用的是含有const int b的定义这时候a _a;其实是a2 _a;


这两个类型中因为只存在一个成员是否是const的差别,一般情况下具有相同的内存布局,所以成员函数(包括构造函数)的调用不会出现太大的问题,但是实际上,这是两套不同的类型生硬的捆绑在一起的结果。

假如通过a2来调用test函数,由于编译器为a1生成的void a1::test和void a2::test的符号完全一致,于是链接器很自然的把void a1::test嫁接在了a2的调用点上,造成了a1和a2难以区分的假象。


第二段代码:

其实和第一段完全一样,foo::foo()作为foo1版本的构造函数,它的链接符号和int main()中的foo2版本完全一样,导致链接器错误的把这个函数嫁接在了foo2的构造过程中。

不过这个例子中,foo1和foo2的内存布局不同,所以由此引发的内存越界访问的错误可以通过一个简单的方法测试:

写如下代码:

#include "a.hpp"
#include <iostream>
int main()
{
    int a;
    foo test;
    int b;
    std::cout << a << std::endl; // 输出a
    std::cout << test.b << std::endl; // 输出1
    std::cout << b << std::endl; // 输出b
}

在一般的实现中a和b其中之一会输出数值2,也就是foo::foo()中的第二个赋值。


值得注意的是,在debug版本下这种对编译器的欺骗很可能不会带来实质性的错误,但是经由release版本的优化之后,两种类型定义的差别会显著增大,一般会导致运行期错误。

事实上这种错误以前就出现过,在使用了第三方代码库的项目中,项目自身定义了某个函数而忘记实现,恰好库中也有一个同名但功能却大相径庭的函数,链接器忠实的把错误的函数链接在了用户的调用点,引起一个难以发现和修改的bug。

我们在代码中使用各种命名规范区分名称,使用namespace隔离潜在同名空间,从而最大程度的避免同名函数/类型的问题。而以这种错误为技巧来制造看似有效的手法,会给工程留下隐患。

 

写到这里,我忽然对早上和释雪探讨的一个问题有了一些认识,问题是这样的:

一个模板
//a.h
template<class T>
class Test
{
};


假如在两个CPP文件中分别以相同的模板参数做了不同的特化(比如写了两个Test<int>的特化代码),会冲突么?答案是不会。而实际使用的特化版本以最先编译的那个单元为准。


要解释这个问题,需要明白另外一件事,就是“模板的代码,在使用的地方必须是可见的”这条规则。

C++ ISO中并没有规定这种行为,那么是什么导致了编译器的这种限制呢?原因就是每个编译单元都不依赖其他单元完成编译,这样一来模板代码就找不到形成OBJ的时机。

具体来说是这样:

假如模板的所有实现代码都存在于某个CPP文件中,当这个CPP文件作为一个编译单元进行解析的时候,因为不知道模板究竟对哪些模板参数进行了实例化,所以也就无从生成OBJ文件。而对所有类型都生成实例在理论上是不可能的。

而在使用了模板的地方,因为不知道模板的实际代码在哪里,也无从为模板生成OBJ文件。这样一来,模板的代码就完全没办法实现了。

于是实际的编译器,应该是采取了这种策略,当编译到使用某个模板的时候,先检查这个模板实例化后生成的符号是否已经存在,如果不存在就根据代码生成实例,并且把符号记录,反之就直接关联已经存在的符号。

由于编译单元的编译顺序完全是由使用者人为控制的,所以编译器自身无法获取首次实例化的时机,那唯一可行的办法就是在每个调用的地方都使得模板代码可见,这样一来无论哪个编译单元首先进行实例化,都可以顺利生成实例。


这条规则有一个隐患是,规则要求的只是可见性,并不要求“同一性”,换句话说,你可以把模板代码写在头文件中,然后所有的使用者都去包含它。也可以把模板的实现代码写在每一个使用它的地方。

如果你使用后一种方法,写在每个文件中的实现代码,可以是各不相同的,能够生成实例的那个版本是首先编译的那个版本。也就是说,程序最终生成的代码,是和编译顺序相关的。

(注:红色部分是原文的写法,不知道是不是我理解有问题,我觉得所谓的"程序最终生成的代码"中关于模板的部分应该是在编译各单元的时候定的,也就是上面所说的在那个编译单元中模板"可见"的部分。这和原文所说的"和编译顺序相关"不是一个意思。)


写了这么多,也不知道问题有没有说清楚。

有兴趣的朋友可以写个代码测试下,不同的编译器也许还有不同的细节差异。

 
操作系统原理:【优麒麟】linux内核添加系统调用 在底端添加如下代码。 阅读详情

相关推荐

C#读取CAD文件(dwg/dxf)并处理

项目要求识别CAD图纸(图纸内容与现实事物比例是1:1)中的内容,并提取出一些关键信息。这里的CAD图纸是指CAD文件,包含多种格式,dwg/dxf/dwf/stl/pdf等等。识别其中内容,主要指文件中的图元信息(也叫实体Entity),指线段、圆弧、矩形等图形元素。得到图元基础信息(比如长度、坐标、粗细)后,进一步做处理(如得到交点、得到线段间间隔、圆弧与线段切点等)。听起来有点难,因为作为软件工程师,对CAD相关事物并不了解,它是未知的;

三千幻想乡 1万+

G++编译报错 warning: iteration *u invokes undefined behavior

G++编译报错 warning: iteration *u invokes undefined behavior 原因:循环限制超过数组的最大下标限制,仔细检查循环限制条件。

u013484572的博客 7562

Chapter 7 Linear Voltage Regulators

这一章我们介绍LDO.

2304_76736886的博客 2763

未定义行为

参考:http://www.itoldme.net/archives/904 C语言中的未定义行为(Undefined Behavior)是指C语言标准未做规定的行为。同时,标准也从没要求编译器判断未定义行为,所以这些行为有编译器自行处理,在不同的编译器可能会产生不同的结果,又或者如果程序调用未定义的行为,可能会成功编译,甚至一开始运行时没有错误,只会在另一个系统上,甚至是在另一个日期运

u011426341的专栏 828

Python爬虫案例:从39问医生网爬取口腔科的在线问答记录(单轮问答)

本次实验爬取39问医生网中口腔科分类下的所有在线单轮问答记录,每页32条,共100页,总数据量为3200条,最终结果以excel格式保存。

JieBao11的博客 1640

软件测试学习笔记(二)图覆盖

软件测试学习笔记(二)图覆盖 课程链接软件测试_中国大学MOOC 2.1 测试中的图 2.2 图覆盖准则 1、什么是图? 点的集合和边的集合。点或者结点,有穷非空的集合。边是点对。 2、图为什么在软件测试中具有重要地位? 所谓软件测试就是将软件变作一张图,然后覆盖它,不仅仅是软件,图更是计算机科学重要的结构。 3、软件测试中的图是如何得到的? 常见的图可以来自源代码,常见的是控制流图,还有各种各样...

DZFinder的博客 1420

warning: iteration xxxu invokes undefined behavior [-Waggressive-loop-optimizations]

for(uint32_t loop = 0; loop < sizeof(data[x]); loop++) { y = a* data[id][loop] + (1 - a) * dataChange; data[id][loop] = y; dataChange= data[id][loop]; } 今天碰到一个摸不到头脑的问题,一直提示我数组越界了,我怎么看都没有越界,确实就那么长,就是这个数组大小,为什么总是提示我越界...

Miya_95的博客 9978

RPA 财务共享中心与RPA赋能

在和集团财务官交流的时候,客户经常会提到FSSC,其中有准备建立FSSC,正在建立FSSC,以及已建成FSSC正使用的,当中会有些对FSSC的疑虑和问题,所以花费些时间对FSSC进行了深入调研,从而也有幸拜读了王兴山、陈剑、梅震、钱自严等专业人士写的几本关于FSSC的书,对FSSC的前因后果有了更深入的认识,同时结合自己在FSSC的一些项目经验,故做了本次分享。 本文章主要分享财务共享中心以及R...

weixin_40244959的博客 2756

c 语言编译错误,C编译错误?

这是由于未定义的行为,您正在访问数组mc的循环的最后一次迭代的边界。一些编译器可以围绕没有未定义的行为的假设执行积极的循环优化。逻辑将类似于以下内容:>访问mc超出范围是未定义的行为>假设没有未定义的行为>因此, 4总是为真,否则mc [di]会调用未定义的行为jmp .L6这几乎与GCC pre-4.8 Breaks Broken SPEC 2006 Benchmarks中概述...

weixin_35483528的博客 567

Coze平台智能体开发实战:打造测试工程师的AI助手

智能体是具备特定身份和能力的AI实体,由大语言模型(LLM)驱动,并通过插件、知识库和工作流扩展其能力。明确的角色定位:可以设定特定身份和专业领域扩展能力:通过插件连接外部工具和服务记忆能力:通过知识库存储和检索专属信息流程化处理:通过工作流执行复杂多步任务通过Coze平台创建测试工程师智能体,可以显著提升测试效率和质量。明确的角色定位:聚焦测试工程师的实际需求丰富的知识库:提供准确的领域知识支持合适的工具集成:通过插件连接现有工具链持续迭代优化:根据反馈不断改进智能体行为自动化测试生成。

hogwarts_beibei的博客 1005

基于STM32设计的消防小车

当前基于STM32单片机设计了一款智能消防小车;当前小车支持烟雾浓度检测、可燃气体检测,环境温度湿度检测、火焰源检测、能支持远程遥控,方便消防员远程操控进行灭火。小车也支持自动消防,在行驶过程中,消防智能小车通过火焰传感器感应火源以检测火焰具体位置,可以利用算法设计进行循迹灭火,该模式下驱动电机水泵喷水,可以有效控制火源的第二次发生,从而完成消防作业。小车本身带了超声波测距模块,红外壁障模块,在自动或者手动控制行驶过程中可以防止碰到障碍物损伤小车。

7375

禁用werror error: iteration 31 invokes undefined behavior [-Werror=aggressive-loop-optimizations]

在相关Makefile里添加。

2302_79939572的博客 329

05 - matlab m_map地学绘图工具基础函数 - 设置比例尺指北针

本篇介绍下m_map中添加指北针(m_northarrow)、比例尺(m_ruler)和进行比例缩放(m_scale)的函数及其用法 。提供了 示例以便参考,罗列了 各函数可设属性。希望对绘图的你有所帮助。

weixin_43339605的博客 1945

关于编译器的一个问题

今天看了下@leekayak 提到的一个问题 http://weibo.com/1465082730/znOSZzU4v        我试图用一个简单的例子来解释下,首先看一段更简单的代码。        #include "stdio.h" #include "stdlib.h" #include int f = 0; int x= 0; void* t1(void*) {

pennyliang的专栏 5721

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

EasyPusher编译时遇到的一个小问题

 下载的EasyPusher-master,因为是VS2010开发的,而我电脑中是VS2015,系统是win10,64位系统,DIRECTX是9.0的版本, 在编译时,报 d3drmobj.h(299): error C2061: 语法错误: 标识符“LPDIRECT3DDEVICE” d3drmobj.h(314): error C2061: 语法错误: 标识符“LPDIRECT3D” 在...

XinShun的博客 1487

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

java eclipse不编译的一个问题

具体的情况是:要搭建起一个“来历不明”的项目、只有我一个开发(无法从队友手中拷贝配置文件很头疼)、项目无法自动编译,其中最有特征的现象是:无法project–clean 解决过程:首先确定了没有犯各种低级错误,比如jdk设置、有未解决的红叉、build source设置错误等。又注意到了无法project–clean很可能不是一般的无法编译问题。又猛然想到,在导入项目时报了一个错,提示缺少org.eclipse.ajdt.ui.ajnature,当时并没有在意,因为导入项目时经常会报一些错误,一般只要项目中

nayi_224的博客 399

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

一个编译不能通过的问题的解决

最近在对数采仪的新系统进行编译时,始终在报一个错误;错误提示很是模糊,好像说与vector有关,但我在程序中根本没有用到呀。 经过艰苦的排查,终于找到罪魁祸首原来是类的构造函数。 在程序中有以下的声明: CDownSerialPort m_DownSerialPortArray[DOWN_SERIAL_PORT_NUM]; 其中CDownSerialPort 是一个类。 而在CDownS...

weixin_30483697的博客 177

一个由__GNUC__引起的编译问题

在前一阵的项目集成过程中遇到一个__GNUC__引起的编译问题,记录下来。     开发平台是linux + oracle proc + database,在编译过程中遇到一个编译错误,报错如下: ...................../ecitype.h PCC-S-02201, Encountered the symbol "aciub8" when expecting one

xueshandiguo的专栏 1万+

CUDA aware 编译时遇到的一个问题

cuda 及openmpi的安装采用帖子这位大神的帖子 安装成功后,测试编译 Makefile 中链接写法为; mpic++  $(LIBFLAGS)  $(SOLVER)  -o $(NAME) 链接不通过 后面将LIBFLAGS选项换到后面 则编译通过,即 mpic++    $(SOLVER)  -o $(NAME) $(LIBFLAGS) 之前的Makefile一直都是

qq_30743171的博客 386

Maven 编译打包一个比较有趣的问题

Java项目中出现包名错误但能正常编译的现象,根源在于编译机制不依赖目录结构,而是基于文件中的package定义。虽然IDEA会报错,但javac编译器会根据源码中的package声明生成正确的class文件路径。这表明class文件的定位本质上是依赖字节码中的全限定名,而非源文件存放目录。这一特性也解释了为何其他JVM语言(如Kotlin、Groovy)生成的class文件同样有效。

fenglllle的博客 420

openH264编译遇到的一个小问题

openH264编译 问题描述: x86_64-w64-mingw32-ar: No such file or directory 原因分析: 更新到最新版本,进到bin目录下未找到对应exe 解决方案: 目录mingw64/bin,x86_64-w64-mingw32-gcc-ar.exe修改成x86_64-w64-mingw32- ar.exe ...

cainiao16204的博客 566

一个不完全编译导致的奇怪问题

分析到这里,你应该明白问题所在了吧,就是a.h修改后,a.c没有重新编译生成a.o, a.out可执行文件link的仍然是初始代码的a.o.gdb 调试,发现a_t结构体的大小再main.c 和 a.c中的size 不一致,一个是12bytes, 一个是8bytes.编译运行,生成a.o, a.out, 运行a.out 输出: 1-2。输入如下:1-2-0,是不是感觉很奇怪, 我们期望的是1-2-3。删除a.o, 重新编译,问题解决。

hzgdiyer的专栏 299

编译Android源码遇到的一个问题

这是我在优快云上写的第一个博客,虽然是一个简单的内容,但是希望这是一个良好的开端。本人接触Android开发也已经有一年时间了,仍处于入门阶段,希望能通过写博客的方式来督促自己不断进步。 接触Android开发接近一年,还从未下载编译过源码。恰逢最近公司新发了台式机,我又动了下载、编译源码的念头。编译源码的教程在网站http://source.android.com/source/downlo

BatmanBruceWayne的博客 919

从一个crash问题展开,探索gcc编译优化

导读:问题分析的过程也正是技术成长之路,本文以一个gcc编译优化引发的crash为切入点,逐步展开对编译器优化细节的探索之路,在分析过程中打开了新世界的大门……背景:一个平平无奇的crash去年,客户提了个bug,并甩给了我们一个Segmentation fault截图,必现crash。这种必现问题我根本不慌的,段错误,无非就是use after free、越界读写等导致的非法内存访问而已。平平无...

高德技术 561

同一个包下的公开类编译时找不到。问题解决

学过java的人都知道,如果在同一个包下,一个类是可以直接引用同个包下的用public修饰的类。 但是有没有试过用doc命令行进去包目录结构下编译一个类时,这个类引用的同个包下的公开类编译时找不到(或许会报ClassNotFoundException),前提是被调用的公开类已经先被编译并通过了。这里提供一个解决方法: 原因:其实这里出现的问题跟源文件没有任何关系,关键错在,你进去包目录下编译文件

DevilDark的博客 6477

从cmake到VS2017编译一个项目遇到的一系列问题

这是一个断断续续持续了一个月的过程,我先来讲一下背景,我们老师买了一个声呐,他有配套的软件和sdk,结果我看了一个礼拜的代码,最后跟我说sdk更新了,我简直说不出话来…这个项目是基于qt和opengl编写的,我对这两个仅停留于知道名字的阶段,而且只会C语言以及忘了差不多的c++,由于篇幅所限,这里就不详细展开qt和opengl的相关知识,这一个月里这个看一点那个看一点恶补了很多知识,而且零零碎碎的基本上成不了系统,在编译的时候出现了各种bug,而且网上的解决方案五花八门,也不一定对你有用,经过我一个月的摸索

layAlex的博客 2513
上一篇: C++编译器如何实现异常处理
下一篇: 星星写的俄罗斯方块
undefined_behavior
博客等级 码龄19年 1粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值