在C++20之前,使用malloc分配int类型内存是否未定义行为?

102

我被告知在C++20之前,以下代码存在未定义的行为:

int *p = (int*)malloc(sizeof(int));
*p = 10;

那是真的吗?

争论的焦点在于在给 int 对象赋值之前,它的生命周期尚未开始 (P0593R6)。为了解决这个问题,应该使用放置 new:

int *p = (int*)malloc(sizeof(int));
new (p) int;
*p = 10;

我们真的需要调用一个对于对象生命周期起始没有实质性作用的默认构造函数吗?

同时,这段代码在纯C中并没有未定义行为。但是如果我在C代码中分配一个 int 并在C++代码中使用它会怎样呢?

// C source code:
int *alloc_int(void)
{
    int *p = (int*)malloc(sizeof(int));
    *p = 10;
    return p;
}

// C++ source code:
extern "C" int *alloc_int(void);

auto p = alloc_int();
*p = 20;

这仍然是未定义行为吗?


8
对于 int 类型?不需要。对于 std::string 类型?需要。 - Eljay
8
对于 int 类型,也是需要的。只是如果不这样做,在实践中可能不会出现问题。对于 std::string 类型,显然会引起问题。 - Barry
9
C++20有哪些新规则改变了这个? - Kevin
4
不应该是 int *p = (int*)malloc(sizeof(int)); p = new(p) int; 吗? 我曾经意识到不给定内存池的返回值也可能会导致致命的影响(尽管看起来有点傻)。 - Scheff's Cat
@Kevin,请查看问题中链接的论文。 - Barry
显示剩余2条评论
2个回答

71

这是真的吗?

是的。从技术上讲,以下任何部分都不能:

int *p = (int*)malloc(sizeof(int));

实际上创建了一个int类型的对象,因此解引用p是未定义行为,因为实际上没有int

我们真的必须调用平凡的默认构造函数来启动对象的生命周期吗?

根据 C++ 对象模型,在 C++20 之前,您需要这样做以避免未定义行为。是的。不这样做会有任何编译器会导致问题吗?据我所知没有。

[...] 这仍然是未定义行为吗?

是的。在 C++20 之前,您仍然没有实际创建任何int对象,因此这是未定义行为。


1
为什么 https://timsong-cpp.github.io/cppwp/n3337/basic.life#1.1 中的语言不足以避免 UB?毕竟,示例中已经为 int 获得了适当大小和对齐方式的存储 - int 对象的生命周期从那里开始。 - avakar
@avakar 因为 [intro.object] 是一个关于对象创建的详尽列表,而“已分配存储空间”不在其中(直到 C++20)。 - Caleth
@Caleth:这种不一致意味着标准存在缺陷。这并没有使[basic.life]失去权威性。(此外,[intro.object]中的那个“详尽列表”说“对象是通过定义、new-expression在需要时由实现创建的”,这足以包括[basic.life]的需求)。 - Ben Voigt
@BenVoigt 除非我们谈论的是 C++20 之前的版本,否则这里的措辞并没有包含“在需要时由实现定义”的部分 - 它 一个详尽的列表: https://timsong-cpp.github.io/cppwp/n4659/intro.object#1 - Barry
@Barry: 我从C++03复制了那个句子,它绝对是C++20之前的版本。你引用的句子的英语语法并没有声称排他性--连词动词"is"在被动语态中使用,而不是为了详细定义"对象的创建"。 - Ben Voigt
显示剩余10条评论

45

是的,这是UB(未定义行为)。列举了存在int的方式列表,除非您认为malloc是非因果关系,否则不适用于其中任何一种情况。

这被广泛认为是标准中的缺陷,但重要性较低,因为C++编译器对该特定UB位周围的优化并不会导致该使用情况出现问题。

至于第二个问题,C++不强制规定C++和C如何交互。因此,与C的所有交互都是UB,即C++标准未定义的行为。


5
你可以详细说明一下一个整数存在的方式列表吗?我记得曾问过类似的问题,关于基本类型存在的寿命,人们告诉我,如果规范没有说明,那么基本类型就可以通过简单地说“它存在”而存在。看来我可能错过了规范中一个有用的部分!我很想知道我应该查看哪个部分! - Cort Ammon
8
C++20中一个对象(任何类型)存在的方式在[intro.object]中列举如下:(1)按定义; (2)通过new表达式; (3)根据P0593中的新规则隐式产生; (4)改变联合体的活动成员; (5)作为临时对象。C++20中新增了(3),而(4)是在C++17中新增的。 - Barry
3
C/C++ 交互确实被认为是未定义行为吗?这似乎更应该是实现定义的,而不是未定义的,否则就连 extern "C" 语法彻底失去了意义。 - Ruslan
4
实现可以自由定义ISO C++未定义的任何行为。(例如gcc -fno-strict-aliasing或默认情况下的MSVC)。说“实现定义”的话,将要求所有C ++实现定义与某些C实现的交互方式,因此,完全由实现决定是否要执行此类操作是有意义的。 - Peter Cordes
4
@PeterCordes: 我想知道为什么很多人不能区分IDB和UB之间的差别,并采用一些奇特的想法,即标准未强制要求所有实现有意义地处理一个结构,这意味着判断没有任何实现应该这样做,而没有这样做的实现必须不被视为劣质。 - supercat
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接