【问题标题】:Malloc(0)ing an array in Windows Visual Studio for C allows the program to run perfectly fine在 Windows Visual Studio for C 中使用 Malloc(0) 处理数组可以让程序完美运行
【发布时间】:2017-01-24 21:27:40
【问题描述】:

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;
        }

    }
}

【问题讨论】:

  • C 根本没有边界检查。甚至对于数组(编译器实际上具有大小)也不行。越界会导致未定义的行为
  • 您可能会调用未定义的行为。您可以溢出在 C 中提供给您的缓冲区 - 这并不意味着这样做是个好主意。您可以确保分配足够的内存,永远不要超出其界限,以及释放它。
  • 另请注意,如果 malloc 应该返回 NULL 或如果您传递 0 作为有效指针作为大小,则取决于 malloc 的实现。
  • “它有效” - 你这么认为。你的程序肯定调用了未定义的行为;结果你观察到的任何东西,并试图将任何形式的“感觉”固定在上面,都是一种天真的努力。说实话,实际上它不幸从你的角度看来是“工作”。如果它坠毁并燃烧,冒烟的残骸将是一个有形的迹象,表明出现了问题。总比让你误以为是正确的感觉要好得多。

标签: c windows malloc dynamic-memory-allocation


【解决方案1】:

关于malloc(0)部分:

来自malloc()man page

malloc() 函数分配 size 字节并返回指向已分配内存的指针。内存未初始化。 如果 size 为 0,则 malloc() 返回 NULL 或稍后可以成功传递给 free() 的唯一指针值。

所以,返回的指针要么是NULL,要么是一个只能传递给free()的指针,你不能指望取消引用该指针并将一些东西存储到内存位置。 p>

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

一旦一个程序命中 UB,它的输出无论如何都不能证明是合理的。

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

也就是说,类比

“你可以分配一个零大小的分配,你不能取消引用它”

一些内存调试器应用程序暗示malloc(0) 的使用可能不安全,并将包括对malloc(0) 的调用在内的语句置于红色区域。

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

关于malloc(&lt;any_size&gt;)部分:

通常,访问越界内存也是 UB。如果你碰巧在分配的内存区域之外访问,你无论如何都会调用UB,并且你推测的结果无法定义。

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

【讨论】:

    【解决方案2】:

    似乎无论我在malloc() 中输入什么,该程序仍然有效。这是为什么呢?

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

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

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

    如果t != NULL,给它加1就是UB,因为给那个指针加1比分配空间多1。


    使用 UB,指针数学可能会像典型的 malloc() 分配大块而不一定是请求的小块一样工作。它可能会在另一个平台/机器或另一个日期或月相上崩溃。即使在轻度测试下,代码也不可靠。

    【讨论】:

      【解决方案3】:

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

      【讨论】:

        【解决方案4】:

        C 标准说:

        如果请求的空间大小为零,则行为是实现定义的;返回的值应为空指针或唯一指针。 [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 之外,你不能用这个指针做任何安全的事情。如果取消引用指针,则允许代码做任何它想做的事情。这就是语言标准中“未定义的行为”的含义。

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

        当您取消引用该分配的开头时,您可能有一些有效内存。你正在做的事情是严格非法的,不可移植的,但是,通过这个实现,可能会起作用。当您进一步索引它时,您可能会开始破坏堆中的其他数据结构(或元数据)。如果您甚至将父亲索引到其中,则由于点击未映射的页面而导致崩溃的可能性越来越大。


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

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

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

        同时,有些程序期望malloc(0) 返回一个空指针,因为这是他们的库一直在做的事情。当标准人员查看现有代码以及它如何使用库时,他们决定如果不“破坏”那里的一些代码,他们就不能选择一种方法而不是另一种方法。所以他们允许 malloc 的行为保持“实现定义”。

        【讨论】:

          猜你喜欢
          • 2020-11-03
          • 2021-12-26
          • 2014-07-20
          • 1970-01-01
          • 1970-01-01
          • 2019-02-28
          • 1970-01-01
          • 2019-12-22
          • 1970-01-01
          相关资源
          最近更新 更多