Depuración de R con código C++/C

Author

George G. Vega Yon, Ph.D.

Published

August 14, 2025

WarningNota de Traducción

Esta versión del capítulo fue traducida de manera automática utilizando IA. El capítulo aún no ha sido revisado por un humano.

Depuración de R con código C++/C

Aunque depurar código R es fácil, lo mismo no aplica para código compilado en R1. Este capítulo muestra algunas formas de depurar tu código R + C++. Necesitarás el GNU Debugger (GDB) y Valgrind.

Antes de comenzar, recuerda que no vamos a lidiar con el buen enfoque de toda la vida Rprint("Tu código está funcionando hasta aquí"). Imprimir mensajes mientras tu programa se ejecuta puede ser muy informativo, pero usar Valgrind y GDB es, en mi humilde opinión, más rápido ya que, la mayoría del tiempo, esos te gritarán, indicando la ubicación de tu problema.

Note

El manual Writing R Extensions (R Core Team 2023) tiene una cantidad considerable de información sobre depurar código compilado en R aquí. Dirk Eddelbuettel (autor principal de Rcpp) tiene un excelente post en Stackoverflow y recomienda un tutorial alojado en BioConductor (Rue-Albrecht et al., n.d.).

Depuración con Valgrind

Como punto de partida, usaremos Valgrind. Valgrind proporciona un marco de trabajo maduro para depuración y perfilado de memoria. Debemos lanzar el programa a través de la línea de comandos para usar un depurador dentro de R. Para lanzar R con Valgrind, usamos lo siguiente:

$ R --debugger=valgrind

Lo cual resultará en algo como lo siguiente:

==31245== Memcheck, a memory error detector
==31245== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==31245== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==31245== Command: /usr/lib/R/bin/exec/R
==31245== 

R version 4.2.3 (2023-03-15) -- "Shortstop Beagle"
Copyright (C) 2023 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

  Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

> 

Una vez que R se ejecuta con Valgrind, el depurador capturará cualquier fuga de memoria generada por tu código C++/C. Lo siguiente es un programa defectuoso de Rcpp que crea un puntero usando new y “olvida” eliminarlo.

#include <Rcpp.h>

using namespace Rcpp;

// [[Rcpp::export]]
NumericVector faulty_program(int n) {

    // Aquí está la línea defectuosa
    NumericVector * x_ptr = new NumericVector(n);

    return *x_ptr;

}

/***R
# Calling the faulty program
faulty_program(10)
*/

Podemos usar la bandera -e en el comando R para compilar el script Rcpp usando sourceCpp:

R --debugger=valgrind -e 'Rcpp::sourceCpp("rcpp-debugging-faulty.cpp")'
==1863== Memcheck, a memory error detector
==1863== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==1863== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==1863== Command: /usr/local/lib/R/bin/exec/R -e Rcpp::sourceCpp("rcpp-debugging-faulty.cpp")
==1863== 

R version 4.5.3 (2026-03-11) -- "Reassured Reassurer"
Copyright (C) 2026 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

  Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

> Rcpp::sourceCpp("rcpp-debugging-faulty.cpp")

> faulty_program(10)
 [1] 0 0 0 0 0 0 0 0 0 0
> 
==1863== 
==1863== HEAP SUMMARY:
==1863==     in use at exit: 60,883,194 bytes in 11,665 blocks
==1863==   total heap usage: 31,690 allocs, 20,025 frees, 93,845,424 bytes allocated
==1863== 
==1863== LEAK SUMMARY:
==1863==    definitely lost: 32 bytes in 1 blocks
==1863==    indirectly lost: 0 bytes in 0 blocks
==1863==      possibly lost: 0 bytes in 0 blocks
==1863==    still reachable: 60,883,162 bytes in 11,664 blocks
==1863==                       of which reachable via heuristic:
==1863==                         newarray           : 4,264 bytes in 1 blocks
==1863==         suppressed: 0 bytes in 0 blocks
==1863== Rerun with --leak-check=full to see details of leaked memory
==1863== 
==1863== For lists of detected and suppressed errors, rerun with: -s
==1863== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Al final de la salida, en la sección LEAK SUMMARY, vemos definitely lost: 24 bytes in 1 block, es decir, una fuga de memoria. Si cambiamos el programa eliminando el puntero antes de retornar, la fuga se resolverá:

Nuevo programa:

    NumericVector res = *x_ptr;
    delete x_ptr;
    
    return res;

Programa anterior

    
    
    
    return *x_ptr;

Re-ejecutando R con Valgrind retorna lo siguiente (solo las últimas pocas líneas):

R --debugger=valgrind -e 'Rcpp::sourceCpp("rcpp-debugging-faulty-fixed.cpp")'
==1920== HEAP SUMMARY:
==1920==     in use at exit: 60,883,841 bytes in 11,664 blocks
==1920==   total heap usage: 31,727 allocs, 20,063 frees, 93,865,871 bytes allocated
==1920== 
==1920== LEAK SUMMARY:
==1920==    definitely lost: 0 bytes in 0 blocks
==1920==    indirectly lost: 0 bytes in 0 blocks
==1920==      possibly lost: 0 bytes in 0 blocks
==1920==    still reachable: 60,883,841 bytes in 11,664 blocks
==1920==                       of which reachable via heuristic:
==1920==                         newarray           : 4,264 bytes in 1 blocks
==1920==         suppressed: 0 bytes in 0 blocks
==1920== Rerun with --leak-check=full to see details of leaked memory
==1920== 
==1920== For lists of detected and suppressed errors, rerun with: -s
==1920== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

No más fugas de memoria.

Usando GDB

A veces, necesitamos ir más allá e inspeccionar qué está pasando dentro del programa. GBD es excelente para eso. Con GBD, podemos establecer puntos de interrupción que nos permiten revisar el programa mientras se ejecuta.

El siguiente código Rcpp genera un error de tipo memory not mapped:

#include <Rcpp.h>

using namespace Rcpp;

// [[Rcpp::export]]
NumericVector faulty_program(int n) {

    // Aquí está la línea defectuosa
    NumericVector * x_ptr;
        
    return *x_ptr;

}

/***R
# Calling the faulty program
faulty_program(10)
*/

En él, tratamos de acceder a una ubicación en la memoria que no ha sido asignada aún, es decir, un NumericVector declarado como un puntero pero nunca asignado. Usar R --debugger=valgrind genera el siguiente código:

==1977== Memcheck, a memory error detector
==1977== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==1977== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==1977== Command: /usr/local/lib/R/bin/exec/R -e Rcpp::sourceCpp("rcpp-debugging-not-mapped.cpp")
==1977== 

R version 4.5.3 (2026-03-11) -- "Reassured Reassurer"
Copyright (C) 2026 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

  Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

> Rcpp::sourceCpp("rcpp-debugging-not-mapped.cpp")

> faulty_program(10)
==1977== Invalid read of size 8
==1977==    at 0x2F34F5D6: get__ (PreserveStorage.h:52)
==1977==    by 0x2F34F5D6: copy__<Rcpp::Vector<14, Rcpp::PreserveStorage> > (PreserveStorage.h:66)
==1977==    by 0x2F34F5D6: Vector (Vector.h:64)
==1977==    by 0x2F34F5D6: faulty_program(int) (rcpp-debugging-not-mapped.cpp:11)
==1977==    by 0x2F34F758: sourceCpp_1_faulty_program (rcpp-debugging-not-mapped.cpp:33)
==1977==    by 0x495DB7D: R_doDotCall (dotcode.c:754)
==1977==    by 0x495E0F6: do_dotcall (dotcode.c:1437)
==1977==    by 0x49ACA5C: Rf_eval (eval.c:1260)
==1977==    by 0x49AE67D: R_execClosure (eval.c:2393)
==1977==    by 0x49AF3D6: applyClosure_core (eval.c:2306)
==1977==    by 0x49AC575: Rf_applyClosure (eval.c:2328)
==1977==    by 0x49AC575: Rf_eval (eval.c:1280)
==1977==    by 0x49B30E3: do_eval (eval.c:3959)
==1977==    by 0x499F3E4: bcEval_loop (eval.c:8118)
==1977==    by 0x49AC069: bcEval (eval.c:7501)
==1977==    by 0x49AC069: bcEval (eval.c:7486)
==1977==    by 0x49AC43A: Rf_eval (eval.c:1167)
==1977==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==1977== 

 *** caught segfault ***
address (nil), cause 'memory not mapped'

Traceback:
 1: .Call(<pointer: 0x2f34f6e0>, n)
 2: faulty_program(10)
 3: eval(ei, envir)
 4: eval(ei, envir)
 5: withVisible(eval(ei, envir))
 6: source(file = srcConn, local = env, echo = echo)
 7: Rcpp::sourceCpp("rcpp-debugging-not-mapped.cpp")
An irrecoverable exception occurred. R is aborting now ...
==1977== 
==1977== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==1977==    at 0x4D6BB2C: __pthread_kill_implementation (pthread_kill.c:44)
==1977==    by 0x4D6BB2C: __pthread_kill_internal (pthread_kill.c:78)
==1977==    by 0x4D6BB2C: pthread_kill@@GLIBC_2.34 (pthread_kill.c:89)
==1977==    by 0x4D1227D: raise (raise.c:26)
==1977==    by 0x4D1232F: ??? (in /usr/lib/x86_64-linux-gnu/libc.so.6)
==1977==    by 0x2F34F5D5: copy__<Rcpp::Vector<14, Rcpp::PreserveStorage> > (PreserveStorage.h:65)
==1977==    by 0x2F34F5D5: Vector (Vector.h:64)
==1977==    by 0x2F34F5D5: faulty_program(int) (rcpp-debugging-not-mapped.cpp:11)
==1977== 
==1977== HEAP SUMMARY:
==1977==     in use at exit: 60,975,521 bytes in 11,879 blocks
==1977==   total heap usage: 31,659 allocs, 19,780 frees, 93,822,460 bytes allocated
==1977== 
==1977== LEAK SUMMARY:
==1977==    definitely lost: 0 bytes in 0 blocks
==1977==    indirectly lost: 0 bytes in 0 blocks
==1977==      possibly lost: 5,987 bytes in 19 blocks
==1977==    still reachable: 60,969,534 bytes in 11,860 blocks
==1977==                       of which reachable via heuristic:
==1977==                         newarray           : 4,264 bytes in 1 blocks
==1977==         suppressed: 0 bytes in 0 blocks
==1977== Rerun with --leak-check=full to see details of leaked memory
==1977== 
==1977== For lists of detected and suppressed errors, rerun with: -s
==1977== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)

Para inspeccionar un error con GDB, tenemos que seguir estos pasos:

  1. Ejecutar R con gdb como depurador: R --debugger=gdb. R no iniciará inmediatamente, así que tenemos tiempo para agregar puntos de interrupción.

  2. Podemos establecer un punto de interrupción en la función dada con break faulty_program. GDB lo capturará sobre la marcha, así que elige yes. Es muy probable que te advierta que no hay símbolo para esa función.

  3. Ejecutar R usando el comando run en gdb:

  4. Obtener el programa usando Rcpp::sourceCpp, y esperar a que gdb pause el programa una vez que alcance el punto de interrupción.

  5. Una vez que el programa se ha pausado, podemos inspeccionar el contexto.

    Debido al número de opciones que tiene, usar GBD puede ser abrumador. Aquí está la lista de comandos que uso más:

    help        # Obtener ayuda
    info locals # Listar las variables locales (alcance)
    info args   # Listar los argumentos pasados a la función
    list        # Ver las últimas pocas líneas del código fuente
    continue    # Continuar ejecutando el programa
    next        # Ejecutar el siguiente paso
    bt          # Mostrar toda la pila de llamadas (backtrace)
    up          # Subir un nivel en la pila de llamadas
    down        # Bajar un nivel en la pila de llamadas
    print       # Imprimir/mostrar una expresión

    Y aquí hay un ejemplo usando info locals, info args, list, y print.

  6. Finalmente, para salir del programa, escribe exit (similar a q() en R.)


  1. Depurar solo código C++/C es fácil, sin embargo. Si ya trabajas con código compilado, debes estar consciente de VS Code y las muchas otras herramientas por ahí para depurar código C++/C.↩︎