【问题标题】:pointer arithmetic in C++ using char*使用 char* 在 C++ 中进行指针运算
【发布时间】:2013-08-24 09:07:21
【问题描述】:

我无法理解这两个代码 sn-ps 之间的区别是什么:

// out is of type char* of size N*D
// N, D are of type int


for (int i=0; i!=N; i++){
    if (i % 1000 == 0){
        std::cout << "i=" << i << std::endl;
    }
    for (int j=0; j!=D; j++) {
        out[i*D + j] = 5;
    }
}

此代码运行良好,即使对于非常大的数据集(N=100000,D=30000)也是如此。根据我对指针运算的理解,这应该给出相同的结果:

for (int i=0; i!=N; i++){
    if (i % 1000 == 0){
        std::cout << "i=" << i << std::endl;
    }
    char* out2 = &out[i*D];
    for (int j=0; j!=D; j++) {
        out2[j] = 5;
    }
}

但是,对于一个非常大的数据集,后者不起作用(它在索引 143886 处冻结 - 我认为它存在段错误,但我不能 100% 确定,因为我不习惯在 Windows 上开发)恐怕我遗漏了一些关于指针算术如何工作的明显内容。会不会和推进char*有关?

编辑:我们现在已经确定问题是索引溢出(即 (i*D + j) >= 2^32),因此使用 uint64_t 而不是 int32_t 解决了问题.我仍然不清楚的是为什么上面的第一个案例会运行,而另一个案例会出现段错误。

【问题讨论】:

  • “不起作用”是什么意思?
  • 你应该显示 out 的声明而不是评论
  • 我当然希望 out 数组的大小至少是 D + D*N,或者你正在不属于你的内存中行走。
  • 您是在 2.793GB 的字符数组上进行此计算的吗?我宁愿认为这不是最佳的。等等,这是 O(N^2) 在外循环中有一个刷新?这需要很多天才能运行
  • @soramimo:我向你保证,发布代码的唯一解释是 fast 是因为它错误

标签: c++ pointers pointer-arithmetic


【解决方案1】:

当使用 N 作为数组的大小时,为什么使用 int? 数组的负值有什么逻辑意义吗?

“不工作”是什么意思?

只需将指针视为内存中的地址,而不是“对象”。

char* 
void*
int*

都是指向内存地址的指针,因此在定义或传入函数时完全相同。

char * a;
int* b = (char*)a;
void* c = (void*)b;

a == b == c;

不同的是,在访问a时,a[i],取回的值是从地址a开始的下一个sizeof(*a)个字节。

当使用 ++ 前进指针时,指针设置的地址会前进

sizeof(pointer_type) bytes.

例子:

char* a = 1;
a++;

a 现在是 2。

((int*)a)++;

a 现在是 6。

另一件事:

char* a = 10;
char* b = a + 10;

&(a[10]) == b

因为最终

a[10] == *((char*)(a + 10))

所以您的示例中的数组大小应该没有问题,因为这两个示例是相同的。

编辑

现在请注意,没有负的内存地址,因此访问带符号负值的数组会将值转换为正值。

int a = -5;
char* data;
data[a] == data[MAX_INT - 5]

出于这个原因,可能是(当使用符号值作为数组大小时!)您的两个示例实际上不会得到相同的结果。

【讨论】:

    【解决方案2】:

    版本 1

    for (int i=0; i!=N; i++) // i starts at 0 and increments until N.  Note:  If you ever skip N, it will loop forever.  You should do < N or <= N instead
    {
        if (i % 1000 == 0) // if i is a multiple of 1000
        {
            std::cout << "i=" << i << std::endl; // print i
        }
    
        for (int j=0; j!=D; j++) // same as with i, only j is going to D (same problem, should be < or <=)
        {
            out[i*D + j] = 5; // this is a way of faking a 2D array by making a large 1D array and doing the math yourself to offset the placement
        }
    }
    

    第 2 版

    for (int i=0; i!=N; i++) // same as before
    {
        if (i % 1000 == 0) // same as before
        {
            std::cout << "i=" << i << std::endl; // same as before
        }
    
        char* out2 = &out[i*D]; // store the location of out[i*D]
        for (int j=0; j!=D; j++) 
        {
            out2[j] = 5; // set out[i*D+j] = 5;
        }
    }
    

    它们在做同样的事情,但如果 out 不够大,它们都会以未定义的方式运行(并且可能会崩溃)。

    【讨论】:

    • 我不确定是谁在贬低这个,但它是准确的。
    • 嗨 Zac,out 实际上是 N*D 的大小,所以它应该足够大。问题似乎实际上是 int32 不足以容纳索引。
    • 这是一个问题,但它远不是发布代码的唯一问题。
    • 鉴于您的第一句话是,“我无法理解这两个代码 sn-ps 之间的区别是什么......”他们做同样事情的解释是正确的。当您尝试溢出 int32 时,两者都会同样搞砸。
    • 你是对的,它们都会产生意想不到的结果,但方式不同。虽然使用 uint64_t 而不是 int32_t 实际上解决了我的问题,但我仍然无法理解这两种情况不同行为的原因。
    【解决方案3】:

    N * D 是 3e9;不适合 32 位 int

    【讨论】:

    • 刚刚开始想这个,我觉得是这个原因!
    • 我在 64 位机器上,我想我可以通过使用 int64 代替 int32 来解决这个问题?
    • 这是如何工作的:out[i*D + j] = 5;?在我看到之前,这是我的第一个猜测。
    • @soramimo 如果你溢出了,你会溢出相同的值,对吧?废话,试试吧,我在这里等了 30 分钟才能看到接受的答案:)。
    • @NemanjaBoric:是的,他的代码将值 5 分配给前 2e9 单元格,然后将值 5 分配给第一个 1e9 再次,而最后一个 1e9 未设置。
    猜你喜欢
    • 2017-09-11
    • 2017-02-14
    • 1970-01-01
    • 2018-06-10
    • 1970-01-01
    • 2013-06-03
    • 2023-03-12
    • 2021-11-26
    • 2011-04-30
    相关资源
    最近更新 更多