为什么这个 for 循环的行为会根据它的编译方式而有所不同?

Why does this for loop behave differently depending on how it is compiled?

提问人:One234Fi 提问时间:11/17/2023 更新时间:11/17/2023 访问量:92

问:

我在编写基于 vulkan-tutorial 的方法时犯了一个错误,直到我更改了我的构建才发现该方法。如果您阅读以下内容,您将看到错误的方法以及更正的方法。错误本身不是我要问的,而是为了完整性:问题是我交换了决定循环长度的变量。出于某种原因,当通过 GCC 一步编译和链接程序时,该方法仍然返回 true,即使由于我的错误它应该返回 false。当我后来更改我的构建以单独将文件编译为对象,然后在后面的步骤中链接它们时,此方法返回了应有的 false。

问:为什么 for 循环在不同的构建中表现不同?

信息:

### "All in one" GCC command used:
gcc $(CFLAGS) -o $(BIN_DIR)/$(NAME) $(SRC_DIR)/\*.c $(LDFLAGS)


### commands used for the multi-step build:
compile step (run per-file):
gcc $(CFLAGS) -c -o $(SRC_DIR)/object1.c $(INCLUDES_FLAGS)

link step:
gcc $(CFLAGS) -o output $(objects) $(LDFLAGS)

Relevant flags used:
CFLAGS = -std=c17 -Wall -Wextra -Wno-unused-parameter -Werror=return-type -pthread -g -O0
LDFLAGS = -lc -lm -lglfw -lvulkan -ldl -lpthread -lX11 -lXxf86vm -lXrandr -lXi -lcglm

方法不正确(将硬编码常量LAYER_COUNT与运行时找到的 layerCount 交换):

const char* validationLayers[] = {
    "VK_LAYER_KHRONOS_validation",
};
const uint32_t LAYER_COUNT = (sizeof(validationLayers) / sizeof(validationLayers[0]));

...

bool checkValidationLayerSupport() {
    uint32_t layerCount;
    vkEnumerateInstanceLayerProperties(&layerCount, NULL);

    VkLayerProperties availableLayers[layerCount];
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);

    const char* layerName;
    for (uint32_t i = 0; i < layerCount; i++) {
        layerName = validationLayers[i];
        bool layerFound = false;

        VkLayerProperties layerProperties;
        for (uint32_t j = 0; j < LAYER_COUNT; j++) {
            layerProperties = availableLayers[j];
            if (strcmp(layerName, layerProperties.layerName) == 0) {
                layerFound = true;
                break;
            }
        }

        if (!layerFound) {
            return false;
        }
    }

    return true;
}

更正方法:

const char* validationLayers[] = {
    "VK_LAYER_KHRONOS_validation",
};
const uint32_t LAYER_COUNT = (sizeof(validationLayers) / sizeof(validationLayers[0]));

...

bool checkValidationLayerSupport() {
    uint32_t layerCount;
    vkEnumerateInstanceLayerProperties(&layerCount, NULL);

    VkLayerProperties availableLayers[layerCount];
    vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);

    const char* layerName;
    for (uint32_t i = 0; i < LAYER_COUNT; i++) {
        layerName = validationLayers[i];
        bool layerFound = false;

        VkLayerProperties layerProperties;
        for (uint32_t j = 0; j < layerCount; j++) {
            layerProperties = availableLayers[j];
            if (strcmp(layerName, layerProperties.layerName) == 0) {
                layerFound = true;
                break;
            }
        }

        if (!layerFound) {
            return false;
        }
    }

    return true;
}

我希望该方法在一个步骤中编译和链接时或作为单独的步骤进行编译和链接时具有一致的行为。我通过在全新安装下运行上述代码并获得相同的行为,确定这不是我的环境或 VulkanSDK 的问题。在这两种情况下,CFLAGS 和 LDFLAGS 变量也是相同的。

C GCC Makefile Vulkan

评论

1赞 Eugene Sh. 11/17/2023
如果 bug 导致数组中的越界访问,则行为是未定义的,不应期望保持一致性。
3赞 gulpr 11/17/2023
你的函数写得很糟糕。您不使用任何参数,只处理全局变量。这是一种非常糟糕的方法。Vulcan 教程绝对不建议它。如果程序的行为因硬件/编译器/编译选项而异,则表明存在未定义行为
2赞 Barmar 11/17/2023
当程序具有未定义的行为时,看似无关紧要的更改通常会产生其他副作用。例如,添加或删除 可能会改变程序是否崩溃。printf()
0赞 Jeremy Friesner 11/17/2023
当试图理解为什么未定义的行为很奇怪时,值得(重新)阅读被盗酒店房间钥匙的类比:stackoverflow.com/a/6445794/131930
3赞 Eugene Sh. 11/17/2023
你错了。具有未定义行为的程序甚至可以正常工作数年或永久,因为即使它是未定义的,它也不会影响这些特定情况下的功能。但是,一旦情况发生变化,它肯定会吹到你的脸上。

答: 暂无答案