我真的需要 malloc 吗?
Do I really need malloc?
我了解到malloc是用来动态分配内存的。在我的代码中,我有时会调用以下函数:
int memory_get_log(unsigned char day, unsigned char date, unsigned char month){
char fileName[11];
unsigned long readItems, itemsToRead;
F_FILE *file;
sprintf(fileName, "%s_%u%u%u%s", "LOG", day, date, month, ".bin");
file = f_open(fileName , "r");
itemsToRead = f_filelength( fileName );
//unsigned char *fileData = (unsigned char *) malloc(itemsToRead);
unsigned char fileData[itemsToRead]; //here I am not using malloc
readItems = f_read(fileData, 1, itemsToRead, file);
transmit_data(fileData, itemsToRead);
f_close(file);
return 0;
}
如您所见,我每次从文件中读取的项目数可能不同。线
unsigned char fileData[itemsToRead];
用于读取这些可变大小的文件。我可以看到我正在以某种方式动态分配内存。这个功能工作正常。我真的需要在这里使用 malloc 吗?
我声明这个数组的方式有什么问题吗?
TL;DR
如果您不知道自己在做什么,请在所有情况下使用 malloc
或固定大小的数组。 VLA:s 根本没有必要。请注意 VLA:s 不能是静态的也不能是全局的。
Do I really need to use malloc
here?
是的。您正在读取文件。它们通常比适用于 VLA 的要大得多。它们应该只用于小型阵列。如果有的话。
长版
Is there anything wrong with the way I am declaring this array?
视情况而定。 VLA:s 已作为强制组件从 C11 中删除,因此严格来说,您使用的是编译器扩展,从而降低了可移植性。将来,VLA:s 可能(可能性极低)从您的编译器中删除。也许您还想在不支持 VLA:s 的编译器上重新编译代码。对此的风险分析取决于您。但我可能会提到 alloca
也是如此。尽管通常可用,但标准并不要求它。
另一个问题是如果分配失败。如果你正在使用 malloc,你有机会从中恢复,但如果你只想做这样的事情:
unsigned char *fileData = malloc(itemsToRead);
if(!fileData)
exit(EXIT_FAILURE);
也就是说,只是在失败时退出而不是尝试恢复,那么就没有关系了。至少从纯恢复的角度来看不是。
而且,尽管 C 标准没有强加任何要求 VLA:s 最终出现在堆栈或堆上,但据我所知,将它们放在堆栈上是很常见的。这意味着由于可用内存不足而导致分配失败的风险要高得多。在 Linux 上,堆栈通常为 8MB,而在 Windows 上为 1MB。在几乎所有情况下,可用堆都高得多。声明 char arr[n]
与 char *arr = alloca(n)
基本相同,但 sizeof
运算符的工作方式除外。
虽然我能理解您有时可能想在 VLA 上使用 sizeof
运算符,但我发现很难找到真正需要它的地方。毕竟,大小永远不会改变,并且在您进行分配时就知道大小。所以代替:
int arr[n];
...
for(int i=0; i<sizeof(arr), ...
就这样:
const size_t size = n;
int arr[size];
...
for(int i=0; i<size; ...
VLA:s 不能替代 malloc
。它们是 alloca
的替代品。如果您不想将 malloc
更改为 alloca
,那么您也不应更改为 VLA。
此外,在许多情况下,VLA 似乎是个好主意,检查大小是否低于某个限制也是一个好主意,如下所示:
int foo(size_t n)
{
if(n > LIMIT) { /* Handle error */ }
int arr[n];
/* Code */
}
这可行,但将其与此进行比较:
int foo(size_t n)
{
int *arr = malloc(n*sizeof(*arr));
if(!arr) { /* Handle error */ }
/* Code */
free(arr);
}
你并没有真正让事情变得更容易。它仍然是一个错误检查,所以你唯一真正摆脱的是 free
调用。我可能还会补充说,由于大小太大而导致 VLA 分配失败的风险要高得多。因此,如果您知道大小很小,则不需要检查,但话又说回来,如果您知道它很小,只需使用适合您需要的常规数组即可。
不过,我不否认VLA:s有一些优点。您可以阅读有关它们的信息 但是在我看来,尽管它们具有这些优势,但并不值得。每当您发现 VLA:s 有用时,我会说您至少应该考虑切换到另一种语言。
此外,VLA:s(以及 alloca
)的优势之一是它们通常比 malloc
更快。因此,如果您遇到性能问题,您可能希望切换到 alloca
而不是 malloc
。 malloc
调用涉及向操作系统(或类似的东西)询问一块内存。然后操作系统搜索它,如果找到它,returns 就是一个指针。另一方面,alloca
调用通常仅通过在一条 cpu 指令中更改堆栈指针来实现。
有很多事情要考虑,但我会避免使用 VLA:s。如果你问我,它们最大的风险是因为它们很容易使用,所以人们对它们变得粗心大意。对于我认为合适的少数情况,我会使用 alloca
代替,因为这样我就不会隐藏危险。
简短摘要
VLA:s C11 及更高版本不需要,因此严格来说,您依赖于编译器扩展。然而,alloca
也是如此。因此,如果这是一个非常大的问题,如果您不想使用 malloc
.
,请使用固定数组
VLA:s 是 alloca
而不是 malloc
的语法糖(并非 100% 正确,尤其是在处理多维数组时)。所以不要用它们代替 malloc
。除了 sizeof
如何在 VLA 上工作之外,它们绝对没有任何好处,除了更简单的声明。
VLA:s(通常)存储在堆栈中,而 malloc 完成的分配(通常)存储在堆中,因此大分配失败的风险要高得多。
您无法检查 VLA 分配是否失败,因此最好提前检查大小是否太大。但是随后我们进行错误检查,就像检查 malloc
returned NULL.
时所做的一样
VLA 不能是全局的也不能是静态的。单独的静态部分可能不会引起任何问题,但如果你想要一个全局数组,那么你不得不使用 malloc
或固定大小的数组。
This function works fine.
不,它没有。它具有未定义的行为。正如 Jonathan Leffler 在评论中指出的那样,数组 fileName
太短了。包含 [=45=]
终止符至少需要 12 个字节。您可以通过更改为:
来使其更安全
snprintf(fileName,
sizeof(fileName),
"%s_%u%u%u%s",
"LOG", day, date, month, ".bin");
在这种情况下,数组太小的问题会通过创建一个扩展名为 .bi
而不是 .bin
的文件来体现,这是一个比未定义行为更好的错误,这是当前的案例.
您的代码中也没有错误检查。我会这样重写它。对于那些认为 goto 不好的人来说,好吧,它通常是不好的,但是错误处理既实用又被经验丰富的 C 编码员普遍接受。另一个常见的用途是打破嵌套循环,但这在此处不适用。
int memory_get_log(unsigned char day, unsigned char date, unsigned char month){
char fileName[12];
unsigned long readItems, itemsToRead;
int ret = 0;
F_FILE *file;
snprintf(fileName,
sizeof(fileName),
"%s_%u%u%u%s", "LOG",
day, date, month, ".bin");
file = f_open(fileName , "r");
if(!file) {
ret = 1;
goto END;
}
itemsToRead = f_filelength( fileName );
unsigned char *fileData = malloc(itemsToRead);
if(!fileData) {
ret=2;
goto CLOSE_FILE;
}
readItems = f_read(fileData, 1, itemsToRead, file);
// Maybe not necessary. I don't know. It's up to you.
if(readItems != itemsToRead) {
ret=3;
goto FREE;
}
// Assuming transmit_data have some kind of error check
if(!transmit_data(fileData, itemsToRead)) {
ret=4;
}
FREE:
free(fileData);
CLOSE_FILE:
f_close(file);
END:
return ret;
}
如果一个函数只有returns 0,那么return任何东西都没有意义。而是将其声明为无效。现在我使用 return 值使调用者可以检测错误和错误类型。
首先,'unsigned char fileData[itemsToRead]' 行要求堆栈内存,如果文件很大,这将是一个可怕的错误。您应该使用 'malloc' 来询问堆上的内存。
其次,如果文件真的足够大,你应该考虑使用虚拟内存或动态加载,如'fseek'方法。
我了解到malloc是用来动态分配内存的。在我的代码中,我有时会调用以下函数:
int memory_get_log(unsigned char day, unsigned char date, unsigned char month){
char fileName[11];
unsigned long readItems, itemsToRead;
F_FILE *file;
sprintf(fileName, "%s_%u%u%u%s", "LOG", day, date, month, ".bin");
file = f_open(fileName , "r");
itemsToRead = f_filelength( fileName );
//unsigned char *fileData = (unsigned char *) malloc(itemsToRead);
unsigned char fileData[itemsToRead]; //here I am not using malloc
readItems = f_read(fileData, 1, itemsToRead, file);
transmit_data(fileData, itemsToRead);
f_close(file);
return 0;
}
如您所见,我每次从文件中读取的项目数可能不同。线
unsigned char fileData[itemsToRead];
用于读取这些可变大小的文件。我可以看到我正在以某种方式动态分配内存。这个功能工作正常。我真的需要在这里使用 malloc 吗?
我声明这个数组的方式有什么问题吗?
TL;DR
如果您不知道自己在做什么,请在所有情况下使用 malloc
或固定大小的数组。 VLA:s 根本没有必要。请注意 VLA:s 不能是静态的也不能是全局的。
Do I really need to use
malloc
here?
是的。您正在读取文件。它们通常比适用于 VLA 的要大得多。它们应该只用于小型阵列。如果有的话。
长版
Is there anything wrong with the way I am declaring this array?
视情况而定。 VLA:s 已作为强制组件从 C11 中删除,因此严格来说,您使用的是编译器扩展,从而降低了可移植性。将来,VLA:s 可能(可能性极低)从您的编译器中删除。也许您还想在不支持 VLA:s 的编译器上重新编译代码。对此的风险分析取决于您。但我可能会提到 alloca
也是如此。尽管通常可用,但标准并不要求它。
另一个问题是如果分配失败。如果你正在使用 malloc,你有机会从中恢复,但如果你只想做这样的事情:
unsigned char *fileData = malloc(itemsToRead);
if(!fileData)
exit(EXIT_FAILURE);
也就是说,只是在失败时退出而不是尝试恢复,那么就没有关系了。至少从纯恢复的角度来看不是。
而且,尽管 C 标准没有强加任何要求 VLA:s 最终出现在堆栈或堆上,但据我所知,将它们放在堆栈上是很常见的。这意味着由于可用内存不足而导致分配失败的风险要高得多。在 Linux 上,堆栈通常为 8MB,而在 Windows 上为 1MB。在几乎所有情况下,可用堆都高得多。声明 char arr[n]
与 char *arr = alloca(n)
基本相同,但 sizeof
运算符的工作方式除外。
虽然我能理解您有时可能想在 VLA 上使用 sizeof
运算符,但我发现很难找到真正需要它的地方。毕竟,大小永远不会改变,并且在您进行分配时就知道大小。所以代替:
int arr[n];
...
for(int i=0; i<sizeof(arr), ...
就这样:
const size_t size = n;
int arr[size];
...
for(int i=0; i<size; ...
VLA:s 不能替代 malloc
。它们是 alloca
的替代品。如果您不想将 malloc
更改为 alloca
,那么您也不应更改为 VLA。
此外,在许多情况下,VLA 似乎是个好主意,检查大小是否低于某个限制也是一个好主意,如下所示:
int foo(size_t n)
{
if(n > LIMIT) { /* Handle error */ }
int arr[n];
/* Code */
}
这可行,但将其与此进行比较:
int foo(size_t n)
{
int *arr = malloc(n*sizeof(*arr));
if(!arr) { /* Handle error */ }
/* Code */
free(arr);
}
你并没有真正让事情变得更容易。它仍然是一个错误检查,所以你唯一真正摆脱的是 free
调用。我可能还会补充说,由于大小太大而导致 VLA 分配失败的风险要高得多。因此,如果您知道大小很小,则不需要检查,但话又说回来,如果您知道它很小,只需使用适合您需要的常规数组即可。
不过,我不否认VLA:s有一些优点。您可以阅读有关它们的信息
此外,VLA:s(以及 alloca
)的优势之一是它们通常比 malloc
更快。因此,如果您遇到性能问题,您可能希望切换到 alloca
而不是 malloc
。 malloc
调用涉及向操作系统(或类似的东西)询问一块内存。然后操作系统搜索它,如果找到它,returns 就是一个指针。另一方面,alloca
调用通常仅通过在一条 cpu 指令中更改堆栈指针来实现。
有很多事情要考虑,但我会避免使用 VLA:s。如果你问我,它们最大的风险是因为它们很容易使用,所以人们对它们变得粗心大意。对于我认为合适的少数情况,我会使用 alloca
代替,因为这样我就不会隐藏危险。
简短摘要
VLA:s C11 及更高版本不需要,因此严格来说,您依赖于编译器扩展。然而,
,请使用固定数组alloca
也是如此。因此,如果这是一个非常大的问题,如果您不想使用malloc
.VLA:s 是
alloca
而不是malloc
的语法糖(并非 100% 正确,尤其是在处理多维数组时)。所以不要用它们代替malloc
。除了sizeof
如何在 VLA 上工作之外,它们绝对没有任何好处,除了更简单的声明。VLA:s(通常)存储在堆栈中,而 malloc 完成的分配(通常)存储在堆中,因此大分配失败的风险要高得多。
您无法检查 VLA 分配是否失败,因此最好提前检查大小是否太大。但是随后我们进行错误检查,就像检查
时所做的一样malloc
returned NULL.VLA 不能是全局的也不能是静态的。单独的静态部分可能不会引起任何问题,但如果你想要一个全局数组,那么你不得不使用
malloc
或固定大小的数组。
This function works fine.
不,它没有。它具有未定义的行为。正如 Jonathan Leffler 在评论中指出的那样,数组 fileName
太短了。包含 [=45=]
终止符至少需要 12 个字节。您可以通过更改为:
snprintf(fileName,
sizeof(fileName),
"%s_%u%u%u%s",
"LOG", day, date, month, ".bin");
在这种情况下,数组太小的问题会通过创建一个扩展名为 .bi
而不是 .bin
的文件来体现,这是一个比未定义行为更好的错误,这是当前的案例.
您的代码中也没有错误检查。我会这样重写它。对于那些认为 goto 不好的人来说,好吧,它通常是不好的,但是错误处理既实用又被经验丰富的 C 编码员普遍接受。另一个常见的用途是打破嵌套循环,但这在此处不适用。
int memory_get_log(unsigned char day, unsigned char date, unsigned char month){
char fileName[12];
unsigned long readItems, itemsToRead;
int ret = 0;
F_FILE *file;
snprintf(fileName,
sizeof(fileName),
"%s_%u%u%u%s", "LOG",
day, date, month, ".bin");
file = f_open(fileName , "r");
if(!file) {
ret = 1;
goto END;
}
itemsToRead = f_filelength( fileName );
unsigned char *fileData = malloc(itemsToRead);
if(!fileData) {
ret=2;
goto CLOSE_FILE;
}
readItems = f_read(fileData, 1, itemsToRead, file);
// Maybe not necessary. I don't know. It's up to you.
if(readItems != itemsToRead) {
ret=3;
goto FREE;
}
// Assuming transmit_data have some kind of error check
if(!transmit_data(fileData, itemsToRead)) {
ret=4;
}
FREE:
free(fileData);
CLOSE_FILE:
f_close(file);
END:
return ret;
}
如果一个函数只有returns 0,那么return任何东西都没有意义。而是将其声明为无效。现在我使用 return 值使调用者可以检测错误和错误类型。
首先,'unsigned char fileData[itemsToRead]' 行要求堆栈内存,如果文件很大,这将是一个可怕的错误。您应该使用 'malloc' 来询问堆上的内存。 其次,如果文件真的足够大,你应该考虑使用虚拟内存或动态加载,如'fseek'方法。