使用Rcpp库在R下书写c++程序遭遇”memory not mapped”错误

但凡遇到内存错误都会让我感觉到头大。因为debug太难,很多时候都感觉无从下手。

这次在使用Rcpp库写了一小段c++程序,用来建一个复杂的树,以提高运行效率,但却遭遇到了”memory not mapped”错误。这个错误出现是有一定概率的。使用相同的代码,在64-bit下的R(OSX)不报错,但是在32-bit下的R(OSX)就报错,在64-bit linux下也报错。

有人说,

This often happens when your C code uses memory that it did not allocate, particularly when it reads or writes just a little beyond the end of a memory block. On some platforms or if you are lucky there is unused memory between blocks of allocated memory and you don’t see a problem. Other machines may pack allocated memory blocks more tightly and writing off the end of an array corrupts another array, leading to the program crashing. (Or reading off the end of an array may pick up data from the next array, which leads to similar crashes later on.) You may also be reading memory which has never been written to, hence you are using random data, which could corrupt things.

郁闷吧。为此,要检查代码是否有内存错误,下载安装Valgrind,然后开始debug。为了方便运行程序,先在R下生成直到调用.Call之前的数据,准备成需要的格式,然后在xcode下新建一个工程,写一个简单的程序利用iostream将其读入c当中来。而后再将原程序除却R至c数据类型转换的部分去除。最后生成一个全新的程序。

先在xcode下将一般的内存错误消灭掉。因为xcode提供有良好的追踪工具,所以一但遇到“EXC_BAD_ACCESS”或者“SIGABRT”之类的可以追踪到具体的代码行。而后依据逻辑以及追踪器给出的变量值将其改正。

接下来,就在terminal中使用

g++ -o out sample.cpp -g -O0
valgrind --leak-check=yes ./out

来查看内存错误。一般的内存错误有几种,内存泄漏memory_leak,无效指针Invalid Pointer,以及未初始化变量Uninitialized Variables。

下面我就大约的讲一下一些具体的例子。
Finding Memory Leaks With Valgrind
程序:

1
2
3
4
5
6
#include <stdlib.h>
int main()
{
    char *x = malloc(100); /* or, in C++, "char *x = new char[100] */
    return 0;
}

Valgrind会报错:

==2330== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2330==    at 0x1B900DD0: malloc (vg_replace_malloc.c:131)
==2330==    by 0x804840F: main (example1.c:5)

首先讲一点,==2330== 并无太大意义,只当它是分隔符就好了。

对于这种错误,很明显是在malloc或者new之后,没有手动释放指针指向的内存。当指针消亡之后,其指向的内存无法被系统自动回收,于是成为了死内存,直至系统重启才能恢复。这就是为什么系统在长时间运行之后内存会显得越来越少,而重启一次会有很大改善的原因。
修改后的程序:

1
2
3
4
5
6
7
8
#include <stdlib.h>
int main()
{
    char *x = malloc(100); /* or, in C++, "char *x = new char[100] */
    delete x; /* 这一步其实就是调用 x->~char()这一析构函数*/
    x = NULL;
    return 0;
}

当然,如果这一类的错误怎么样都无法消除的话,并不是会引发程序的崩溃,所以其实可以忍。

Finding Invalid Pointer Use With Valgrind

程序;

1
2
3
4
5
6
7
8
#include <stdlib.h>
 
int main()
{
    char *x = malloc(10);
    x[10] = 'a';
    return 0;
}

Valgrind会报错:

==9814==  Invalid write of size 1
==9814==    at 0x804841E: main (example2.c:6)
==9814==  Address 0x1BA3607A is 0 bytes after a block of size 10 alloc'd
==9814==    at 0x1B900DD0: malloc (vg_replace_malloc.c:131)
==9814==    by 0x804840F: main (example2.c:5)

因为c不检查边界,所以这类的错误很容易发生。你分配给x 10个sizeof(char)大小的内存(10bytes),但是你却希望在第11byte的位置写入数据,这显然就写到边界外面去了。
修改后的程序:

1
2
3
4
5
6
7
8
#include <stdlib.h>
 
int main()
{
    char *x = malloc(10*sizeof(char));
    for(int i=0; i<10; i++) x[i] = 'a';
    return 0;
}

Detecting The Use Of Uninitialized Variables

程序;

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main()
{
    int x;
    if(x == 0)
        return 1;
    return 0;
}

Valgrind会报错:

==17943== Conditional jump or move depends on uninitialised value(s)
==17943==    at 0x804840A: main (example3.c:6)

未初始的值无法用来比较。所以这个修改起来也应该很简单的。

修改后的程序:

1
2
3
4
5
6
7
8
9
#include <stdio.h>
 
int main()
{
    int x=0;
    if(x == 0)
        return 1;
    return 0;
}

以上看起来似乎很简单,但其实真正你会遇到那种毫无逻辑的场面。只能看你的耐心了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注