设置数组元素时出现MSVC访问冲突

时间:2014-08-21 16:21:17

标签: c++ c memory-management

我一直在努力寻找对以下代码中出现的错误的解释:

#include <stdlib.h>

int main() {
    int m=65536;
    int n=65536;
    float *a;

    a = (float *)malloc(m*n*sizeof(float));

    for (int i = 0; i < m; i++){
       for (int j = 0; j < n; j++){
            a[i*n + j] =  0;  
        }    
    }
    return 0;
}

为什么我会收到&#34;访问违规行为&#34;执行此程序时出错?

内存分配是成功的,问题出在一些迭代计数的嵌套for循环中。我尝试使用较小的m&amp; n值并且该程序有效。

这是否意味着我内存不足?

2 个答案:

答案 0 :(得分:7)

问题是m*n*sizeof(float)可能是溢出,导致价值相对较小。因此malloc可以正常工作,但它没有像你期望的那样分配那么多的内存,因此你会跑掉缓冲区的末尾。

具体来说,如果int是32位宽(这是常见的),则65336 * 65336已经是溢出,因为您需要至少33位来表示它。 C ++中的有符号整数溢出(我相信C)会导致未定义的行为,但是一个常见的结果是最重要的位被删除,而你留下较低的位。在你的情况下,它给出0.那么然后乘以sizeof(float),但零乘以任何东西仍为零。

所以你试图分配0个字节。事实证明malloc会让你这样做,它会返回一个有效的指针,而不是一个空指针(如果分配失败,你会得到它)。 (参见下面的编辑。)

所以你有一个有效的指针,但取消引用它是无效的。您完全取消引用它的这一事实是实现的副作用:为了生成一个不会被重用的唯一地址,这就是当您要求0字节时要求malloc执行的操作,malloc可能分配了一个小但非零的字节数。当您尝试远远超出这些参考时,通常会遇到访问冲突。

修改

事实证明,当请求0字节时malloc执行的操作可能取决于您使用的是C还是C ++。在过去,C标准需要一个0字节的malloc来返回一个唯一的指针作为生成“特殊”指针值的方法。在现代C ++中,未定义0字节的malloc(参见C ++ 11标准的3.7.4.1节中的脚注35)。当我最初写答案时,我还没有意识到malloc的API已经以这种方式发生了变化。 (我喜欢它,当一个新手问题让我学习新的东西。)VC ++ 2013似乎保留了旧的行为(返回一个唯一的指针,分配0字节),即使在编译C ++时也是如此。

答案 1 :(得分:2)

你是2个问题的受害者。

首先是尺寸计算:

正如有些人指出的那样,你超出了size_t的范围。您可以使用以下代码验证要分配的大小:

cout << "Max size_t is:   " << SIZE_MAX<<endl;
cout << "Max int is :     " << INT_MAX<<endl; 
long long lsz = static_cast<long long>(m)*n*sizeof(float);  // long long to see theoretical result
size_t sz = m*n*sizeof(float);  // real result with overflow as will be used by malloc 
cout << "Expected size:   " << lsz << endl;
cout << "Requested size_t:" << sz << endl;

您会感到惊讶,但是对于MSVC13,由于溢出(!!),您要求0字节。您可能会使用不同的编译器获得另一个数字(导致低于预期的大小)。

其次,malloc()可能会返回一个问题指针:

malloc()的调用可能显示为成功,因为它不会返回nullptr。分配的内存可能小于预期。甚至请求0字节也可能显示为成功,如文档here所示:如果size为零,则返回值取决于特定的库实现(它可能是也可能不是空指针),但返回指针不得解除引用。

float *a = reinterpret_cast<float*>(malloc(m*n*sizeof(float)));  // prefer casts in future
if (a == nullptr)
    cout << "Big trouble !";   // will not be called 

替代方案

如果你绝对想要使用C,更喜欢calloc(),你至少会得到一个空指针,因为该函数会注意到你有溢出:

float *b = reinterpret_cast<float*>(calloc(m,n*sizeof(float)));

更好的方法将使用运算符 new []

float *c = new (std::nothrow) float[m*n];  // this is the C++ way to do it
if (c == nullptr)
    cout << "new Big trouble !";
else {
    cout << "\nnew Array:           " << c << endl;
    c[n*m-1] = 3.0;    // check that last elements are accessible
}  

<强> 编辑: 它还受size_t限制。

编辑2: 遇到问题时,new[]会抛出bad_alloc个例外,甚至bad_array_new_length。如果你愿意,你可以尝试/捕捉这些。但如果您希望在内存不足时获得nullptr,则必须使用Beat评论中指出的(std::nothrow)

对于你的情况,最佳方法,如果你真的需要这些大量的花车,那就是为了向量。因为它们也受size_t限制,但实际上你有2D数组,你可以使用向量向量(如果你有足够的内存):

vector <vector<float>> v (n, vector<float>(m));