gpt4 book ai didi

c++ - 当涉及数组时,我们能否从 C++ 安全地调用 C API 函数?

转载 作者:搜寻专家 更新时间:2023-10-31 00:27:41 24 4
gpt4 key购买 nike

上下文:

作为一名老 C 程序员(甚至是 K&R C...),我一直认为数组只不过是连续分配的非空对象集特定的成员对象类型,称为元素类型(来自 C11 标准的 n1570 草案,6.2.5 类型)。因此,我并不太担心指针算法。

我现在知道数组是一种对象类型,它只能通过定义 (6.1)、通过新表达式 (8.3.4) 创建,当隐式更改数组的事件成员时 union (12.3),或创建临时对象时 (7.4, 15.2)(来自 C++17 的 n4659 草案)。

问题:

我必须使用一个 C 库,其中一些函数返回指向 C 结构数组的指针。到目前为止一切顺利,C 结构是 POD 类型,正确的填充和对齐是通过使用编译器的标准标志实现的。但是由于数组的大小只有在运行时才知道,即使有正确的 extern "C" 声明,我的函数被声明为返回一个指向数组第一个元素的指针 - 实际大小是由 API 的不同函数返回。

简化示例:

#include <iostream>

extern "C" {
struct Elt {
int ival;
//...
};

void *libinit(); // initialize the library and get a handle
size_t getNElts(void *id); // get the number of elements
struct Elt* getElts(void *id); // get an access the the array of elements
void libend(void *id); // releases library internal data
}

int main() {
void *libid = libinit();
Elt* elts = getElts(libid);
size_t nelts = getNElts(libid);
for(int i=0; i<nelts; i++) {
std::cout << elts[i].ival << " "; // is elts[i] legal?
}
std::cout << std::endl;
libend(libid);
return 0;
}

问题:

我知道内存块可能是通过 malloc 分配的,这允许在其上使用指针,我假设 getElts(libid)[0]不涉及未定义的行为。但是,在从未将 C 数组声明为 C++ 数组时,对 C 数组使用指针算术是否合法:API 仅保证我有一组连续分配的 Elt 类型对象 并且getElts 返回指向该集合第一个元素的指针。

因为 [expr.add] 明确限制数组内的指针算法:

4 When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i + j] if 0 <= i + j <=n; otherwise, the behavior is undefined...

这曾经是一种常见的做法...

编辑

为了更清楚地说明我的问题,我知道如果用 C++ 完成,this 将是 UB

libstub.c++

/* C++ simulation of a C implementation */
extern "C" {
struct Elt {
int ival;
//...
};

void *libinit(); // initialize the library and get a handle
size_t getNElts(void *id); // get the number of elements
struct Elt* getElts(void *id); // get an access the the array of elements
void libend(void *id); // releases library internal data
}

size_t getCurrentSize() {
return 1024; // let us assume that the returned value is not a constexpr
}

void *libinit() {
size_t N = getCurrentSize();
unsigned char * storage = new unsigned char[(N + 1) * sizeof(Elt)];
// storage can provide storage for a size_t correct alignment
size_t *n = new(storage) size_t;
*n = N;
for (size_t i=1; i<=N; i++) {
// storage can provide storage for a size_t, correct alignment
Elt *elt = new(storage + (i+1) * sizeof(Elt)) Elt();
elt->ival = i; // put values into elt...
}
return static_cast<void *>(storage);
}

void libend(void * id) {
unsigned char *storage = static_cast<unsigned char *>(id); // ok, back cast is valid
delete[] storage; // ok, was allocated by new[]
}

size_t getNElts(void *id) {
size_t *n = reinterpret_cast<size_t *>(id); // ok a size_t was created there
return *n;
}

Elt *getElts(void *id) {
unsigned char *storage = static_cast<unsigned char *>(id); // ok, back cast
Elt* elt = reinterpret_cast<Elt *>(storage + sizeof(Elt)); // ok an Elt was created there
return elt;
}

这是有效的 C++ 代码,它满足 C API 要求。问题是 getElts 返回一个指向不属于任何数组成员的单个元素对象的指针。于是根据getElts返回值的[expr.add]指针算法调用UB

最佳答案

标准提供几乎为零的互操作性保证 .

就 C++ 而言,C 代码中发生的事情超出了 C++ 的范围。

所以“这个指针是否指向数组”是 C++ 标准无法回答的问题,因为指针来自 C 函数。相反,这是留给您的特定编译器的问题。

在实践中,这是可行的。理论上,当您以任何方式与 C 交互时,C++ 无法保证您的程序结构良好。


这是个好消息,因为 C++ 标准在创建类型 T 的动态数组方面被打破了。糟糕的是,没有编译器魔术就没有符合标准的方法来实现 std::vector ,或者由编译器定义因尝试执行此操作而导致的未定义行为。

但是,C++ 编译器可以自由地完全忽略这个问题。当对象被连续分配以表现得像数组时,它们可以自由定义元素间指针算法。我不知道有哪个编译器正式声明或保证这一点。

同样,他们可以自由地提供任何关于他们如何处理 C 代码中的指针的保证。在实践中,当您与 C 代码交互时,它们确实提供了相当合理的行为。

我不知道任何编译器有任何正式的保证。

关于c++ - 当涉及数组时,我们能否从 C++ 安全地调用 C API 函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48111688/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com