C/C++是如何知道动态分配数组的长度的?
这个问题让我困扰了一段时间。
比如我写了 int* a = new int[n]
,这时候我只有一个指针,它指向数组 a 的开头,但 C/C++ 是怎么知道 n
的呢?我知道如果我想把这个数组传给另一个函数,我必须把数组的长度也传过去,所以我猜 C/C++ 并不真的知道这个数组有多长。
我知道我们可以通过查找 NUL 终止符来推断字符数组 char*
的结束位置。但是对于其他数组,比如 int,是否也有类似的机制呢?同时,char 不仅仅可以表示一个字符——它也可以被当作整数类型。那么 C++ 是怎么知道这个数组的结束位置的呢?
当我在开发嵌入式 Python 时,这个问题让我更加困惑(如果你对嵌入式 Python 不熟悉,可以忽略这一段,只回答上面的问题,我还是很感激的)。在 Python 中有一个 "ByteArray",而将这个 "ByteArray" 转换为 C/C++ 的唯一方法是使用 PyString_AsString() 将其转换为 char*。但是如果这个 ByteArray 中包含 0,那么 C/C++ 会认为 char* 数组提前结束。这还不是最糟糕的部分。最糟糕的是,假设我写了一个
char* arr = PyString_AsString(something)
void* pt = calloc(1, 1000);
如果 st 恰好以 0 开头,那么 C/C++ 几乎可以保证会清空 arr 中的所有内容,因为它认为 arr 在遇到 NULL 后就结束了。然后它可能会通过给 pt 分配一块内存来清空 arr 中的所有内容。
非常感谢你的时间!我真的很感激。
3 个回答
C/C++语言并不知道数组的长度,所以你很容易就会出现越界访问数组的情况。C/C++对字符数组的长度也是一无所知。
Char*可以指向字符串,但它并不等同于一个字符串。在C/C++中,字符串是以NULL(空字符)来结束的,这是一种约定。
让我们来看看反汇编器!C和C++的处理方式是不同的。关于C语言中free
是如何工作的在另一个问题中有讨论,这里我们来看看C++中的工作原理:
struct T {
~T();
int data;
};
void test(T* p)
{
delete[] p;
}
接下来,我们运行编译器来生成汇编代码。这里是为i386编译的相关部分:
movl -4(%edi), %eax
leal (%edi,%eax,4), %esi
cmpl %esi, %edi
je L4
.align 4,0x90
L8:
subl $4, %esi
movl %esi, (%esp)
call L__ZN1TD1Ev$stub
cmpl %esi, %edi
jne L8
你可以看到重要的部分:在p
的开始之前存储了一个整数,这个整数表示p
的长度,然后代码会循环遍历p
数组,为数组中的每个项调用析构函数。接着它会调用delete
,通常这很简单,因为它只是调用了free
(C语言的函数)。所以你可以看到C++中的delete
是如何用free
来表示的。
析构函数和异常:根据上面的汇编代码,你可以注意到如果T
的析构函数抛出了异常,那么p
数组的一部分会调用析构函数,而数组的其余部分则不会。析构函数绝对不应该抛出异常。
注意事项:这只是你的编译器和运行时解决这个问题的一种可能方式。(在这里,析构函数是由编译器生成的代码调用的,而delete
是运行时的一部分。)这些实现方式有很多灵活性,你的实现可能会不同。这也说明了为什么你应该始终调用正确的操作符,delete[]
或delete
——调用错误的会导致各种麻烦,比如破坏内存和释放无效指针。
关于NUL终止符:NUL终止符之所以会成为问题,是因为PyString_AsString
和其他类似的函数会调用strlen
来确定字符串的长度。然而,free
并不关心NUL终止符,它会单独跟踪来自原始malloc
调用的长度。对于PyString_AsString
(以及strdup
等),这并不是一个选项,因为没有便携的方式来获取一块内存的大小——malloc
和free
并没有提供这个功能。此外,你可以将一个指针传递给PyString_AsString
,这个指针可能位于malloc
块的中间或完全不同的地方。
C/C++ 语言并不关心字符串的长度;是 分配器(就是实现 malloc()
、free()
等功能的小代码)知道字符串有多长。C/C++ 可以随心所欲,不用担心长度的限制。
另外,PyString_AsStringAndSize()
也是相关的内容。