- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
上下文:
作为一名老 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/
C语言sscanf()函数:从字符串中读取指定格式的数据 头文件: ?
最近,我有一个关于工作预评估的问题,即使查询了每个功能的工作原理,我也不知道如何解决。这是一个伪代码。 下面是一个名为foo()的函数,该函数将被传递一个值并返回一个值。如果将以下值传递给foo函数,
CStr 函数 返回表达式,该表达式已被转换为 String 子类型的 Variant。 CStr(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CSng 函数 返回表达式,该表达式已被转换为 Single 子类型的 Variant。 CSng(expression) expression 参数是任意有效的表达式。 说明 通常,可
CreateObject 函数 创建并返回对 Automation 对象的引用。 CreateObject(servername.typename [, location]) 参数 serv
Cos 函数 返回某个角的余弦值。 Cos(number) number 参数可以是任何将某个角表示为弧度的有效数值表达式。 说明 Cos 函数取某个角并返回直角三角形两边的比值。此比值是
CLng 函数 返回表达式,此表达式已被转换为 Long 子类型的 Variant。 CLng(expression) expression 参数是任意有效的表达式。 说明 通常,您可以使
CInt 函数 返回表达式,此表达式已被转换为 Integer 子类型的 Variant。 CInt(expression) expression 参数是任意有效的表达式。 说明 通常,可
Chr 函数 返回与指定的 ANSI 字符代码相对应的字符。 Chr(charcode) charcode 参数是可以标识字符的数字。 说明 从 0 到 31 的数字表示标准的不可打印的
CDbl 函数 返回表达式,此表达式已被转换为 Double 子类型的 Variant。 CDbl(expression) expression 参数是任意有效的表达式。 说明 通常,您可
CDate 函数 返回表达式,此表达式已被转换为 Date 子类型的 Variant。 CDate(date) date 参数是任意有效的日期表达式。 说明 IsDate 函数用于判断 d
CCur 函数 返回表达式,此表达式已被转换为 Currency 子类型的 Variant。 CCur(expression) expression 参数是任意有效的表达式。 说明 通常,
CByte 函数 返回表达式,此表达式已被转换为 Byte 子类型的 Variant。 CByte(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CBool 函数 返回表达式,此表达式已转换为 Boolean 子类型的 Variant。 CBool(expression) expression 是任意有效的表达式。 说明 如果 ex
Atn 函数 返回数值的反正切值。 Atn(number) number 参数可以是任意有效的数值表达式。 说明 Atn 函数计算直角三角形两个边的比值 (number) 并返回对应角的弧
Asc 函数 返回与字符串的第一个字母对应的 ANSI 字符代码。 Asc(string) string 参数是任意有效的字符串表达式。如果 string 参数未包含字符,则将发生运行时错误。
Array 函数 返回包含数组的 Variant。 Array(arglist) arglist 参数是赋给包含在 Variant 中的数组元素的值的列表(用逗号分隔)。如果没有指定此参数,则
Abs 函数 返回数字的绝对值。 Abs(number) number 参数可以是任意有效的数值表达式。如果 number 包含 Null,则返回 Null;如果是未初始化变量,则返回 0。
FormatPercent 函数 返回表达式,此表达式已被格式化为尾随有 % 符号的百分比(乘以 100 )。 FormatPercent(expression[,NumDigitsAfterD
FormatNumber 函数 返回表达式,此表达式已被格式化为数值。 FormatNumber( expression [,NumDigitsAfterDecimal [,Inc
我是一名优秀的程序员,十分优秀!