Malloc(0)ing array in Windows Visual Studio for C 允许程序 运行 完美无缺

Malloc(0)ing an array in Windows Visual Studio for C allows the program to run perfectly fine

C 程序是一种 Damereau-Levenshtein 算法,它使用矩阵来比较两个字符串。在 main() 的第四行,我想要 malloc() 矩阵(二维数组)的内存。在测试中,我 malloc'd (0) 并且它仍然运行完美。似乎无论我输入 malloc(),程序仍然有效。这是为什么?

我在 Visual Studio 开发人员命令提示符下使用 "cl" 命令编译了代码,没有出现任何错误。

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>


int main(){

    char y[] = "felkjfdsalkjfdsalkjfdsa;lkj";
    char x[] = "lknewvds;lklkjgdsalk";
    int xl = strlen(x);
    int yl = strlen(y);
    int** t = malloc(0);
    int *data = t + yl + 1; //to fill the new arrays with pointers to arrays
    for(int i=0;i<yl+1;i++){
        t[i] = data + i * (xl+1); //fills array with pointer
    }
    for(int i=0;i<yl+1;i++){
        for(int j=0;j<xl+1;j++){
            t[i][j] = 0; //nulls the whole array
        }
    }

    printf("%s", "\nDistance: ");
    printf("%i", distance(y, x, t, xl, yl));
    for(int i=0; i<yl+1;i++){
        for(int j=0;j<xl+1;j++){
            if(j==0){
                printf("\n");
                printf("%s", "| ");
            }
            printf("%i", t[i][j]);
            printf("%s", " | ");
        }
    }


}
int distance(char* y, char* x, int** t, int xl, int yl){
    int isSub;
    for(int i=1; i<yl+1;i++){
        t[i][0] = i;
    }
    for(int j=1; j<xl+1;j++){
        t[0][j] = j;
    }



    for(int i=1; i<yl+1;i++){
        for(int j=1; j<xl+1;j++){
            if(*(y+(i-1)) == *(x+(j-1))){
                isSub = 0;

            }
            else{
                isSub = 1;

            }
            t[i][j] = minimum(t[i-1][j]+1, t[i][j-1]+1, t[i-1][j-1]+isSub); //kooks left, above, and diagonal topleft for minimum
            if((*(y+(i-1)) == *(x+(i-2))) && (*(y+(i-2)) == *(x+(i-1)))){ //looks at neighbor characters, if equal

                t[i][j] = minimum(t[i][j], t[i-2][j-2]+1, 9999999); //since minimum needs 3 args, i include a large number
            }



        }
    }


    return t[yl][xl];
}

int minimum(int a, int b, int c){ 
    if(a < b){
        if(a < c){
            return a;
        }
        if(c < a){
            return c;
        }
        return a;
    }
    if(b < a){
        if(b < c){
            return b;
        }
        if(c < b){
            return c;
        }
        return b;
    }
    if(a==b){
        if(a < c){
            return a;
        }
        if(c < a){
            return c;
        }

    }
}

关于 malloc(0) 部分:

来自malloc()man page

The malloc() function allocates size bytes and returns a pointer to the allocated memory. The memory is not initialized. If size is 0, then malloc() returns either NULL, or a unique pointer value that can later be successfully passed to free().

因此,返回的指针要么是 NULL,要么是只能传递给 free() 的指针,您不能指望取消引用该指针并存储 某些东西进入内存位置。

在上述任一情况下,您试图使用一个 无效 的指针,它会调用 undefined behavior.

一旦一个程序命中UB,它的输出无论如何都无法证明。

UB 的主要成果之一是“工作正常”(如“错误”预期的那样)。

也就是说,类比

"you can allocate a zero-sized allocation, you just must not dereference it"

一些内存调试器应用程序暗示使用 malloc(0) 可能是不安全的,并且将包括对 malloc(0).

的调用的语句标记为红色区域

Here's a nice reference related to the topic, if you're interested.

关于 malloc(<any_size>) 部分:

一般来说,再次访问超出限制的内存是 UB。如果你碰巧在分配的内存区域之外访问,你无论如何都会调用UB,并且你推测的结果无法定义。

FWIW,C 本身不会强加/执行任何边界检查。因此,您不会“限制”(读作“编译器错误”)访问超出绑定的内存,但这样做会调用 UB。

It seems that whatever I put in malloc(), the program still works. Why is this?

int** t = malloc(0);
int *data = t + yl + 1;

t + yl + 1 是未定义的行为 (UB)。其余代码无关紧要。

如果 t == NULL,向其加 1 是 UB,因为向空指针加 1 是无效的指针数学。

如果 t != NULL,向其加 1 是 UB,因为向该指针加 1 超过了分配的 1 space。


对于 UB,指针数学可能会像希望的那样工作,因为典型的 malloc() 分配大块,不一定是请求的小块。它可能会在另一 platform/machine 或另一天或月相崩溃。该代码即使与光测试一起使用也不可靠。

你真幸运。 C 不进行严格的边界检查,因为它有性能成本。将 C 程序想象成在私人建筑中举行的喧闹派对,OS 警察就驻守在外面。如果有人扔了一块留在俱乐部内的石头(一个无效写入的例子,它违反了过程中的所有权约定但仍在俱乐部边界内)警察看不到它发生并且不采取任何行动。但是,如果石头被抛出并危险地飞出 window(操作系统注意到的违规示例),OS 警察会介入并关闭聚会。

C 标准说:

If the size of the space requested is zero, the behavior is implementation-defined; the value returned shall be either a null pointer or a unique pointer. [7.10.3]

所以我们必须检查您的实施说明。问题是 "Visual Studio," 所以让我们检查 Visual C++ 的页面是否有 malloc:

If size is 0, malloc allocates a zero-length item in the heap and returns a valid pointer to that item.

因此,使用 Visual C++,我们知道您将获得有效指针而不是空指针。

但它只是一个指向零长度项目的指针,所以除了将它传递给 free 之外,您可以使用该指针做任何安全的事情。如果取消引用指针,则代码可以做任何它想做的事。这就是语言标准中 "undefined behavior" 的含义。

那么为什么它看起来有效?可能是因为 malloc return 将指针指向至少几个字节的有效内存,因为 malloc 为您提供指向零长度项目的有效指针的最简单方法是假装您真的要求至少一个字节。然后对齐规则会将其四舍五入为 8 个字节。

当您取消引用该分配的开头时,您可能有一些有效内存。你所做的是严格非法的,不可移植的,但是,通过这种实现,可能会起作用。当您进一步索引它时,您可能会开始破坏堆中的其他数据结构(或元数据)。如果您将 father 索引到其中,您将越来越有可能因访问未映射的页面而崩溃。


为什么标准允许 malloc(0) 实现定义而不是仅仅要求它 return 一个空指针?

对于指针,有时需要特殊值。最明显的是空指针。空指针只是一个保留地址,永远不会用于有效内存。但是,如果您想要另一个对您的程序有意义的特殊指针值怎么办?

在标准出现之前的黑暗日子里,一些 malloc 允许您通过调用 malloc(0) 有效地保留额外的特殊指针值。他们本可以使用 malloc(1) 或任何其他非常小的尺寸,但 malloc(0) 明确表示您只想保留和地址而不是实际的 space。所以有很多程序依赖于这种行为。

与此同时,有些程序期望 malloc(0) 到 return 一个空指针,因为这是他们的库一直在做的事情。当标准人员查看现有代码及其如何使用库时,他们决定如果没有 "breaking" 一些代码,他们不能选择一种方法而不是另一种方法。所以他们允许 malloc 的行为保持 "implementation-defined."