前言
本文主要讲述变量名冲突的问题。
不同于已删除的出版,本文增加了部分深层拓展。并经过更加细致的校对。
\\color{orange}免责声明
由于版本问题,您遇到的情况可能与我所讲述的不符,一切以本机版本为准,如果您遇到了其他问题,欢迎在评论区留下您的观点或遇到的问题。
现在变量名问题没有得到解决,我仍不建议大家在 NOI 系列比赛中使用这些变量名。
本人所用配置是:
- Microsoft:
Windows 10
- Dev C++:
5.11- MinGW-w64:
4.9.2- Linux stu:
20.04
- Geany:
1.36- G++:
Ubuntu 9.4.0- Luogu IDE:
C++14(GCC 9)
Update
2026/10/19 更正谬误。
2026/10/20 补充说明 Luogu IDE 在系统环境下的报错和 NOI Linux 的报错。更新续集。
------感谢 BIxuan 学姐 的启发。
正文
引入
在机房颓的时候突然发现自己之前写的一道学校改编的入门考试题(原题在这里),
我这样写:
cpp
double len(double x1, double y1, double x2, double y2){
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
按照现在的一般说法 ,这应该会 CE,因为 x1 y1 不能用作变量名。
但是它 AC 了。
?不是说不行吗?
于是 cao2333 开始了查资料 -> 验证 -> 不对 -> 查资料的循环......
一点小知识:
#ifdef Value:如果Value值有相关定义则会执行下面的语句。#ifndef Value:与#ifdef恰好相反,当后面的Value没有被不定义时才会执行。#endif:表示终止条件判断,与#ifdef和#ifndef等等连用。#include<filename>导入头文件#define A B宏定义,告诉编译器遇到A就当成B。#undef A表示取消#define的宏定义。
cmath
<cmath>文件是标准库头文件的数学库。在这个库中,包括了abs(绝对值)sin cos tan(三角函数)ceil floor(向上/向下取整)等等十分好用的函数的指向。
有很多说法指出,x1 等变量名被占用是 cmath 头文件的锅。那么我们扒一下他的代码:
P.S. 省略 1600 多行代码。
......算了,还是用搜索吧。
搜索结果:未找到"x1" 未找到"y1"。
很好看来这个问题并没有出在 cmath 文件。
那么为什么很多人加了 cmath 会有问题、不加就没问题呢?
我们仔细审视一下 cmath 文件,发现这里的很多函数用几个模糊的大写字母来计算,大部分函数只有不到十行,这显然不足以完成仅仅推导式就要十几行的数学函数。
实际上,<cmath> 主要提供指向和重载,具体实现由数学库提供。它的性质与 bits/stdc++.h 相似,都是将一些零散的头文件合并,并将深层头文件内容 、本文件中的额外附加 以及执行条件一并添加到前端用户编写的程序中。
因此如果深层的头文件出现问题,依赖该文件的其他文件也会报错。
在 cmath 的开头部分中,我们找到以下代码:
cpp
#pragma GCC system_header
#include <bits/c++config.h>
#include <bits/cpp_type_traits.h>
#include <ext/type_traits.h>
#include <math.h>
看来问题就出在这几个头文件里了。
math.h
经过递归搜索大法,问题大致出在 C:\Program Files (x86)\Dev-Cpp\MinGW64\x86_64-w64-mingw32\include\math.h 上。
<math.h>头文件中,存储了部分函数的定义、运算方式 和 大部分数学函数的指向 (声明)。包括但不限于fabssincos等。
然后观察到:
cpp
_CRTIMP double __cdecl j0 (double) __MINGW_ATTRIB_DEPRECATED_MSVC2005;
_CRTIMP double __cdecl j1 (double) __MINGW_ATTRIB_DEPRECATED_MSVC2005;
_CRTIMP double __cdecl jn (int, double) __MINGW_ATTRIB_DEPRECATED_MSVC2005;
_CRTIMP double __cdecl y0 (double) __MINGW_ATTRIB_DEPRECATED_MSVC2005;
_CRTIMP double __cdecl y1 (double) __MINGW_ATTRIB_DEPRECATED_MSVC2005;
_CRTIMP double __cdecl yn (int, double) __MINGW_ATTRIB_DEPRECATED_MSVC2005;
冲突变量名定义在这里咕。据说是 MSVC 兼容的私货,并不是标准库函数(他们到底怎么混进去的啊),至于后面的内容是什么意思就不得而知了。在 math.h 中没有同名的变量名和宏定义。
续集
受诸位学长学姐的启发,我再次检验了 Luogu IDE 的报错情况。
发现对于这段代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int x1,y0,y1,yn;
int main(){
return 0;
}
- 在 Luogu IDE 上:
cpp
/tmp/compiler_28o4pg7i/src:3:8: 错误:'int y0' redeclared as different kind of entity
3 | int x1,y0,y1,yn;
| ^~
/tmp/compiler_28o4pg7i/src:3:11: 错误:'int y1' redeclared as different kind of entity
3 | int x1,y0,y1,yn;
| ^~
/tmp/compiler_28o4pg7i/src:3:14: 错误:'int yn' redeclared as different kind of entity
3 | int x1,y0,y1,yn;
| ^~
- 在 Dev Cpp 上没有报错。
- 在 NOI Linux 上与 Luogu IDE 报错相同。
啊?又有问题了?
结论
这两段代码的主要区别在于 y1 变量定义的位置。如果在全局定义,就会出问题;如果在局部定义就能正常跑。
其原理很简单的咕。如果你有试过在一份码里面定义几个同名的变量还能过编就是跑出来是依托:
在 C++ 函数内部定义的变量名与全局函数重名,一般以小范围局部定义优先。
如果将变量名定义在正常小范围函数中,会因为优先级的原因认定用户所定义的变量名,覆盖掉全局的函数。
也就是说,在局部定义变量名是 OK 的,编译器也认,在全局就要尽量避免与库中所有函数重名。
不得不说 Dev Cpp 这个老东西还是太神奇了,这个历史遗留问题竟然被他躲过去了 awa。
拓展 Q & A & D(Discovery)
在扒代码的过程中,我还有一些问题和有趣的发现:
Q:网上的说法哪一个正确?
A:不知道。
说实在的,我也不能保证我说的是对的,建议大家也可以扒一扒 Cpp 的代码。
不过值得指出的是,事实上命名 x1,x2 等等变量名并不会产生报错。因为标准库中不含有相应的冲突函数名。不管在哪一个标准库文件里都不能找到名为 x1 的全局变量或函数,不会在代码实现中产生冲突。
Q:这几个函数是干什么用的?
A:在这里贴出 Bing 搜索的结果:
- \(j0,j1,jn\) 用于返回第一类贝塞尔函数解;
- \(y0,y1,yn\) 用于返回第二类贝塞尔函数解;
这里摘取部分百度百科内容:
贝塞尔函数(Bessel functions)是数学上的一类特殊函数,通常用于解决涉及圆对称或球对称的微分方程,其解无法用初等函数表示。
它以 19 世纪德国天文学家弗里德里希·威廉·贝塞尔命名,最早由丹尼尔·伯努利在研究悬链振动时提出,并由欧拉、拉格朗日等数学家进一步发展。
- 第一类贝塞尔函数 \(J_n(x)\):在 x=0 处有限,常用于描述物理问题中中心点有界的情况。
- 第二类贝塞尔函数 \(Y_n(x)\)(又称诺依曼函数 \(N_n(x)\)):在 x=0 处发散,用于补充第一类函数形成通解。
- 第三类贝塞尔函数(汉克尔函数 \(H_n^{1}(x)\), \(H_n^{2}(x)\)):由第一类和第二类函数组合而成,常用于波动问题的辐射边界条件。
- 修正贝塞尔函数 \(I_n(x)\) 和 \(K_n(x)\):用于处理指数型增长或衰减问题,如热传导或扩散方程。
停停停,给我干哪来了?这还是计算机学吗?如果感兴趣的话可以去看看偶,没准你是下一个新算法的发明者。
Q:为什么我在本地过编了?
A:因为本地只要不会载入相关头文件,或者不满足石山一般的 源代码条件,就不会将 <cmath> 中的这几行库函数导入。不过我的 Dev Cpp 真的神秘避开,咱也不知道为啥。
Q:这些数学函数实现只有这么几行吗?
A:当然不是。上面提到,这些函数后面跟了一大段不明字母。这些字母是什么意思呢?
询问蓝色小鲸鱼......
这些函数和 <cmath> 中的函数一样,都是汇总过来的。至于为什么找不到后面的大写字母和源码呢?
因为我们已经挖的太深了,再深就是底层文件了。一般情况下,这部分函数前面带有 __cdecl(调用约定),说明这些东西已经是代码和系统 约定 好了的,一般会在 .dll(Windows) 或者 .so(Linux)中实现。那就不是我这个 OIer 能设计的领域喽,如果有后端开发大佬能看到还希望能够略加说明咕~
D:源代码打表
在 math.h 函数中:
cpp
#define M_E 2.7182818284590452354
#define M_LOG2E 1.4426950408889634074
#define M_LOG10E 0.43429448190325182765
#define M_LN2 0.69314718055994530942
#define M_LN10 2.30258509299404568402
#define M_PI 3.14159265358979323846
#define M_PI_2 1.57079632679489661923
#define M_PI_4 0.78539816339744830962
#define M_1_PI 0.31830988618379067154
#define M_2_PI 0.63661977236758134308
#define M_2_SQRTPI 1.12837916709551257390
#define M_SQRT2 1.41421356237309504880
#define M_SQRT1_2 0.70710678118654752440
呵,想必大家都知道这片源代码在干什么吧......
赤裸裸的打表啊!
D:两个 math.h
在搜索时,C:\Program Files (x86)\Dev-Cpp\MinGW64\lib\gcc\x86_64-w64-mingw32\4.9.2\include\c++\tr1 路径下还有一份 math.h 文件。这份文件中有大量的 using tr1::...:: 代码。大概是 tr1 的兼容代码。不知道是做什么的,今天按下不表。
D:变量名通缉单
在 <cmath> 文件中,断断续续出现了下列代码:
\\color{blue}过长,已折叠
cpp
#undef abs
#undef div
#undef acos
#undef asin
#undef atan
#undef atan2
#undef ceil
#undef cos
#undef cosh
#undef exp
#undef fabs
#undef floor
#undef fmod
#undef frexp
#undef ldexp
#undef log
#undef log10
#undef modf
#undef pow
#undef sin
#undef sinh
#undef sqrt
#undef tan
#undef tanh
#undef fpclassify
#undef isfinite
#undef isinf
#undef isnan
#undef isnormal
#undef signbit
#undef isgreater
#undef isgreaterequal
#undef isless
#undef islessequal
#undef islessgreater
#undef isunordered
#undef acosh
#undef acoshf
#undef acoshl
#undef asinh
#undef asinhf
#undef asinhl
#undef atanh
#undef atanhf
#undef atanhl
#undef cbrt
#undef cbrtf
#undef cbrtl
#undef copysign
#undef copysignf
#undef copysignl
#undef erf
#undef erff
#undef erfl
#undef erfc
#undef erfcf
#undef erfcl
#undef exp2
#undef exp2f
#undef exp2l
#undef expm1
#undef expm1f
#undef expm1l
#undef fdim
#undef fdimf
#undef fdiml
#undef fma
#undef fmaf
#undef fmal
#undef fmax
#undef fmaxf
#undef fmaxl
#undef fmin
#undef fminf
#undef fminl
#undef hypot
#undef hypotf
#undef hypotl
#undef ilogb
#undef ilogbf
#undef ilogbl
#undef lgamma
#undef lgammaf
#undef lgammal
#undef llrint
#undef llrintf
#undef llrintl
#undef llround
#undef llroundf
#undef llroundl
#undef log1p
#undef log1pf
#undef log1pl
#undef log2
#undef log2f
#undef log2l
#undef logb
#undef logbf
#undef logbl
#undef lrint
#undef lrintf
#undef lrintl
#undef lround
#undef lroundf
#undef lroundl
#undef nan
#undef nanf
#undef nanl
#undef nearbyint
#undef nearbyintf
#undef nearbyintl
#undef nextafter
#undef nextafterf
#undef nextafterl
#undef nexttoward
#undef nexttowardf
#undef nexttowardl
#undef remainder
#undef remainderf
#undef remainderl
#undef remquo
#undef remquof
#undef remquol
#undef rint
#undef rintf
#undef rintl
#undef round
#undef roundf
#undef roundl
#undef scalbln
#undef scalblnf
#undef scalblnl
#undef scalbn
#undef scalbnf
#undef scalbnl
#undef tgamma
#undef tgammaf
#undef tgammal
#undef trunc
#undef truncf
#undef truncl
实测发现,在这张列表里涉及到的所有 #undef 后面的命名 全部有对应函数 ,都不能作为全局变量名。
后记
其实也算是颓了一个下午。有一种探案的感觉呢。不过在查资料的过程中,我发现了这样一个问题:
大家要么是查到资料解决问题就好,要么是发现问题避开问题,很少的巨佬会身先士卒追根究底、不找到根因不罢休。
我也不是什么巨佬啦,我的调查就到这里了,如果有很多人支持的话,我应该会出第二期 QwQ,如果有巨佬收到启发,希望诸位巨佬也要坚持到底呀!
我在第一次下笔下键盘的时候多少太急躁了,所以出现了大量的谬误,请大家多多海涵咕......
AI 使用说明
++本文未经过任何 AI 的润色++ ,仅在知识参考和校对方面采纳了 DeepSeek Pro 的意见,++保证人类贡献远大于非人类贡献++。
\\\color{green}The\\;\\;End \\