C++中传递未初始化变量给函数是否为UB?UBSan为何未检测到?
问题场景
我在编写光线追踪器时遇到了一个bug,相关代码如下:
定义的Length函数
double Length(const Vector& v) { return std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); }
出错的赋值语句
Vector normal = Length(normal) < eps ? intersection.GetNormal() : baricentric.GetNormal();
这里的normal是刚声明的局部变量,未初始化就被传入Length函数作为参数,函数内部访问了它的成员,导致程序运行异常,最终通过gdb调试才定位到问题。
我使用Clang搭配以下CMake编译标志构建项目:
set(CMAKE_CXX_FLAGS_ASAN "-g -fsanitize=address,undefined -fno-sanitize-recover=all" CACHE STRING "Compiler flags in asan build" FORCE)
但编译过程没有报错,运行时也未触发ASAN或UBSAN的错误提示。
疑问解答
该情况是否属于未定义行为(UB)?
是。根据C++标准,使用未初始化对象的值属于明确的未定义行为。这里normal作为局部变量声明后未初始化,就被传递给Length函数,函数内部直接访问了它的成员,完全符合UB的定义。UBSan是否应该检测此类bug?
UBSan对未初始化变量的检测有范围限制:它默认主要针对基本数据类型的未初始化使用,对于用户自定义类型(比如这里的Vector),不会自动追踪其内部成员的初始化状态。只有当未初始化的值被实际使用并影响程序可观测行为时,才有可能触发检测,但针对自定义类型的这类场景,UBSan通常不会主动告警。是否是编译器优化(如RVO)导致未被检测到?
RVO(返回值优化)本身不会直接导致这个问题被隐藏,更核心的原因是UBSan对自定义类型的未初始化检测支持不足。当然,某些优化可能会改变未初始化值的读取路径,间接绕过UBSan的检测,但即使关闭优化,UBSan依然大概率无法检测到自定义类型成员的未初始化访问。使用-O0是否能关闭所有优化,还是需要其他标志?
-O0是Clang/GCC的默认优化级别,会关闭绝大多数会改变程序语义的优化,但编译器仍会做一些必要的内部转换。不过针对这个问题,-O0无法让UBSan检测到自定义类型的未初始化成员访问,因为这是UBSan本身的检测范围限制,和优化级别无关。如果需要更全面的内存初始化检测,可以尝试使用-fsanitize=memory(MSan),但MSan要求程序和所有依赖库都用MSan编译,使用成本较高。
内容的提问来源于stack exchange,提问作者Greg

