CMake和GoogleTest将构建类型从调试更改为发布时的怪异行为与比较

0mkxixxg  于 2022-11-11  发布在  Go
关注(0)|答案(2)|浏览(155)

上下文

我正在写一个函数,它计算定时器应用程序的某个指数值。它只需要2^x,直到某个阈值maxVal,在这种情况下,应该返回阈值。而且,在所有情况下,边缘情况应该被接受。
util.cpp:


# include "util.h"

# include <iostream>

int calculateExponentialBackoffDelay(int x, int maxVal)
{
    int y;

    if(x <= 0 || maxVal <= 0) return maxVal;
    else if (x > maxVal) return maxVal;

    y = std::pow(2, x);
    std::cout << "y = " << y << std::endl;

    if(y > maxVal) return maxVal;
    else if(y < 0) return maxVal;
    else return y;
}

现在,我使用GoogleTest依赖项提取来创建一个CMake配置。
CMakeLists.txt:

cmake_minimum_required(VERSION 3.14)
project(my_project)

# GoogleTest requires at least C++14

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_BUILD_TYPE Release)

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG release-1.12.1
)

# For Windows: Prevent overriding the parent project's compiler/linker settings

set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

enable_testing()
include_directories(
    ${CMAKE_CURRENT_LIST_DIR}/
    )

# Add the main source compilation units

add_executable(
  test_calc
  test_calc.cpp
  util.cpp
)
target_link_libraries(
  test_calc
  GTest::gtest_main
)

include(GoogleTest)
gtest_discover_tests(test_calc)

我在我写的函数上运行了一个测试,测试了一些边界条件,其中之一是测试如果2^x〉maxVal,那么maxVal应该被返回(因为2^x的结果高于最大值),这就是阈值。
测试_计算.cpp:


# include "util.h"

# include <climits>

# include <gtest/gtest.h>

TEST(util_tests, ExponentialBackoff)
{
    int x, maxVal, res;

    // Test x = maxVal and maxVal = 1000
    // Expected output: 1000
    maxVal = 1000;
    x = maxVal;
    EXPECT_EQ(maxVal, calculateExponentialBackoffDelay(x, maxVal));
}

当我将x和maxVal设为1000时,计算出的结果是2^(1000),因为它是一个很大的数字,所以会溢出/回绕到一个真正的负数(-2 147 483 ′ 648),这是意料之中的,因此我的测试会期望x=1000,maxVal=1000的结果〈0。

问题

这就是事情出乎意料的发展。我在我的build目录中运行ctest,所有的测试用例都通过了。然后我在CMakeLists.txt中将一行从...:

set(CMAKE_BUILD_TYPE Debug)

...到...

set(CMAKE_BUILD_TYPE Release)

...并且该测试用例失败:

1: Expected equality of these values:
1:   maxVal
1:     Which is: 1000
1:   calculateExponentialBackoffDelay(x, maxVal)
1:     Which is: -2147483648

因此,由于某种原因,函数体中y〈0的情况没有达到,相反,我们返回了环绕结果。
为什么会这样?我做错了什么?我试着用Linux strip -s test_calc检查它是否是一个符号,同时在CMake中保留调试配置,结果发现测试用例仍然通过。CMake还做了什么来改变结果二进制文件的比较行为?

svmlkihl

svmlkihl1#

浮点-积分转换

  • 浮点类型的纯右值可以转换为任何整数类型的纯右值。小数部分会被截断,也就是说,小数部分会被舍弃。如果该值无法放入目的类型,则行为未定义。-就是这样。

行为未定义。对int y = std::pow(2, x)x > 31的结果的任何预期都被视为无效,并可能导致函数calculateExponentialBackoffDelay的任何结果。
在这种特殊情况下,编译器知道y = std::pow(2, x)对于x的有效值总是大于0,并丢弃分支if (y < 0)

emeijp43

emeijp432#

当改变构建类型改变结果时,你首先想到的应该是“代码中某处的未定义行为”。事实确实如此。将doublepow的结果)转换为整数类型,其值超出该整数类型的范围,这就是未定义行为。
引用cppreference的话:
任何真实的浮点类型的有限值都可以隐式转换为任何整数类型。除了上面的布尔转换所涉及的情况外,规则如下:

  • 小数部分被丢弃(朝零截断)。
  • 如果结果值可以由目标类型表示,则使用该值
  • 否则,行为未定义

2的1000次幂大约是1e301,这远远超出了int类型的任何可能范围,因此将其转换为y = std::pow(2, x);绝对是一个UB。
下面是一个尝试来解释它:
1.编译器可以基于代码中没有UB的假设来优化代码。
1.除非有UB,y = std::pow(2, x);的唯一有效结果是正整数(因为我们得到的是正整数的指数,它只能是0或正数)
1.在程序中可以没有UB(点1),因此条件if (y < 0)总是false(点2),并且可以被优化掉。
但这只是我的猜测,它可能是正确的,也可能是完全错误的。编译器被允许对包含任何UB的代码做任何事情。

相关问题