C 中的数组
在 C 中,您要记住数组只是内存中的一个地址,加上一个长度和一个对象类型。当您将其作为参数传递给函数或函数的返回值时,长度会被遗忘,并且会与第一个元素的地址互换处理。这导致程序在读取或写入超出缓冲区末尾时出现许多安全漏洞。
在大多数情况下,数组的名称会自动转换为其第一个元素的地址,因此您可以将数组或指针传递给memmove(),但也有一些例外情况,即它也有长度很重要。数组上的sizeof() 运算符是数组中的字节数,但sizeof() 指针是指针变量的大小。所以如果我们声明int a[SIZE];,sizeof(a) 与sizeof(int)*(size_t)(SIZE) 相同,而sizeof(&a[0]) 与sizeof(int*) 相同。另一个重要的一点是,编译器通常可以在编译时判断数组访问是否越界,但它不知道对指针的哪些访问是安全的。
如何返回一个数组
如果你想返回一个指向同一个静态数组的指针,并且每次调用函数都得到同一个数组,你可以这样做:
#define ARRAY_SIZE 32U
int* get_static_array(void)
{
static int the_array[ARRAY_SIZE];
return the_array;
}
您必须不在静态数组上调用free()。
如果你想创建一个动态数组,你可以这样做,虽然这是一个人为的例子:
#include <stdlib.h>
int* make_dynamic_array(size_t n)
// Returns an array that you must free with free().
{
return calloc( n, sizeof(int) );
}
当您不再需要动态数组时,必须使用free() 释放它,否则程序会泄漏内存。
实用建议
对于任何简单的事情,你实际上会写:
int * const p = calloc( n, sizeof(int) );
除非由于某种原因数组指针会改变,例如:
int* p = calloc( n, sizeof(int) );
/* ... */
p = realloc( p, new_size );
我会推荐calloc() 而不是malloc() 作为一般规则,因为它将内存块初始化为零,而malloc() 未指定内容。这意味着,如果您有读取未初始化内存的错误,使用calloc() 将始终为您提供可预测、可重现的结果,而使用malloc() 每次都可能为您提供不同的未定义行为。特别是,如果您分配一个指针,然后在 0 是指针的陷阱值的实现上取消引用它(如典型的桌面 CPU),则由 calloc() 创建的指针总是会立即给您一个段错误,而垃圾malloc() 创建的指针可能看起来可以工作,但会损坏内存的任何部分。这种错误很难追踪。在调试器中也更容易看出内存是否被清零,而不是任意值是有效还是垃圾。
进一步讨论
在 cmets 中,有人反对我使用的一些术语。特别是,C++ 提供了几种不同的方法来返回对数组的引用,这些引用保留了有关其类型的更多信息,例如:
#include <array>
#include <cstdlib>
using std::size_t;
constexpr size_t size = 16U;
using int_array = int[size];
int_array& get_static_array()
{
static int the_array[size];
return the_array;
}
std::array<int, size>& get_static_std_array()
{
static std::array<int, size> the_array;
return the_array;
}
因此,一位评论者(如果我理解正确的话)反对“返回数组”这一短语应该仅指代这种函数。我使用的短语比这更广泛,但我希望能澄清当你在 C 中 return the_array; 时会发生什么。你会得到一个指针。与您相关的是您丢失了有关数组大小的信息,这使得在 C 中编写安全错误非常容易,这些错误会读取或写入超出为数组分配的内存块。
还有一些反对意见,我不应该告诉你使用calloc() 而不是malloc() 动态分配包含指针的结构和数组,如果你之前取消引用这些指针,几乎所有现代 CPU 都会出现段错误你初始化它们。郑重声明:并非所有 CPU 都如此,因此它不是可移植的行为。有些 CPU 不会陷阱。一些旧的大型机将捕获一个非零的特殊指针值。但是,当我在台式机或工作站上编码时,它会派上用场。即使您在其中一个异常上运行,至少您的指针每次都会具有相同的值,这应该使错误更具重现性,并且当您调试并查看指针时,它会立即明显为零,而指针是垃圾不会立即显而易见。