您可以在不调用构造函数的情况下调用攻击器吗?

Can you call the destructor without calling the constructor?

本文关键字:调用 情况下 攻击 构造函数      更新时间:2023-10-16

我一直在尝试在不需要时不初始化内存,并且正在使用malloc数组来做:

这就是我运行的:

#include <iostream>
struct test
{
    int num = 3;
    test() { std::cout << "Initn"; }
    ~test() { std::cout << "Destroyed: " << num << "n"; }
};
int main()
{
    test* array = (test*)malloc(3 * sizeof(test));
    for (int i = 0; i < 3; i += 1)
    {
        std::cout << array[i].num << "n";
        array[i].num = i;
        //new(array + i) i; placement new is not being used
        std::cout << array[i].num << "n";
    }
    for (int i = 0; i < 3; i += 1)
    {
        (array + i)->~test();
    }
    free(array);
    return 0;
}

输出:

0 ->- 0
0 ->- 1
0 ->- 2
Destroyed: 0
Destroyed: 1
Destroyed: 2

尽管没有构造数组索引。这是"健康"吗?也就是说,我可以简单地将破坏者视为"只是功能"吗?(除了驱动器对数据成员相对于我指定的指针的位置有隐式知识之外(

(

只是指定:我不是在寻找有关C 适当使用的警告。我简单地想知道使用这种无构造方法时是否应该警惕。

(脚注:我不想使用构造函数的原因是因为很多次,内存根本不需要初始化,并且这样做很慢(

不,这是不确定的行为。对象的寿命在呼叫构造函数完成后开始,因此,如果从未调用构造函数,则该对象在技术上永远不会存在。

这种可能"似乎"在您的示例中表现正确,因为您的结构是微不足道的(int :: 〜int是一个no-op(。

您还泄漏了内存(驱动器破坏给定的对象,但是通过malloc分配的原始内存仍然需要为free D(。

编辑:您可能还想查看这个问题,因为这是一个非常相似的情况,只需使用堆栈分配而不是malloc即可。这给出了围绕对象寿命和构造的标准中的一些实际报价。

我也会添加它:如果您不使用新的位置并且显然需要它(例如,struct包含一些容器类或VTable等(,您将遇到真正的麻烦。在这种情况下,省略安置新呼叫几乎可以肯定会为您带来0个非常脆弱的代码的绩效收益 - 无论哪种方式,这都是一个好主意。

是的,驱动器不过是功能。您可以随时调用它。但是,在没有匹配的构造函数的情况下称其为一个坏主意。

因此,该规则是:如果您未将内存初始化为特定类型,则可能不会解释和使用该内存作为该类型的对象;否则是未定义的行为。(用charunsigned char作为例外(。

让我们通过对您的代码进行行分析进行行。

test* array = (test*)malloc(3 * sizeof(test));

此行使用系统提供的内存地址初始化指针标量array。请注意,对于任何类型的类型,内存>不初始化。这意味着您不应将这些内存视为任何对象(即使像int这样的标量,请撇开您的test类类型(。

后来,您写道:

std::cout << array[i].num << "n";

这将内存作为test类型,违反了上述规则,导致了不确定的行为。

及以后:

(array + i)->~test();

您再次使用了test类型的内存!调用驱动器也使用对象!这也是ub。

在您的情况下,您很幸运,没有任何有害发生,并且您会得到合理的事情。但是,瑞银完全取决于您的编译器的实施。它甚至可以决定格式化您的磁盘,并且仍然是标准配合的。

也就是说,我可以简单地将驱动器视为"只是功能"?

否。尽管它在许多方面都像其他功能一样,但灾难有一些特殊功能。这些归结为类似于手动内存管理的模式。就像内存分配和交易需要成对的一样,建筑和破坏也是如此。如果跳过一个,请跳过另一个。如果您致电一个,请致电另一个。如果您坚持手动内存管理,那么施工和破坏的工具将是新的,并且明确地称为灾难。(使用newdelete的代码将分配和构造合并为一个步骤,而破坏和交易合并组合到另一个步骤中。(

不要跳过将要使用的对象的构造函数。这是不确定的行为。此外,构造函数越少,如果您跳过它,就越有可能出错。也就是说,随着您节省更多的时间,您会打破更多。跳过使用的对象的构造函数并不是提高效率&mdash的一种方式。这是编写破码的方法。效率低下,正确的代码胜过无效的高效代码。

一点点沮丧:这种低级管理可能会成为时间的巨大投资。只有在绩效回报的现实机会时才走这条路线。不要仅仅为了优化而将代码复杂化。还要考虑更简单的替代方案,这些替代方案可能会获得类似的结果,而较少的代码开销。也许除了以某种方式标记未初始化的对象外,没有执行初始化的构造函数?(细节和可行性取决于所涉及的类,因此扩展了这个问题的范围。(

一点点鼓励:如果您考虑标准库,则应该意识到自己的目标是可以实现的。我将提出vector::reserve作为可以在不初始化内存的情况下分配内存的示例。

您当前在从不存在的对象访问字段时,您当前拥有UB。

您可以通过执行构造函数NOOP来使字段非初始化。然后,编译器可能很容易进行初始化,例如:

struct test
{
    int num; // no = 3
    test() { std::cout << "Initn"; } // num not initalized
    ~test() { std::cout << "Destroyed: " << num << "n"; }
};

演示

对于可读性,您可能应该将其包装在专门的类中,例如:

struct uninitialized_tag {};
struct uninitializable_int
{
    uninitializable_int(uninitialized_tag) {} // No initalization
    uninitializable_int(int num) : num(num) {}
    int num;
};

demo

相关文章: