Depuración de R con código C++/C
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.
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=valgrindLo 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:
Ejecutar R con gdb como depurador:
R --debugger=gdb. R no iniciará inmediatamente, así que tenemos tiempo para agregar puntos de interrupción.
Podemos establecer un punto de interrupción en la función dada con
break faulty_program. GDB lo capturará sobre la marcha, así que eligeyes. Es muy probable que te advierta que no hay símbolo para esa función.
Ejecutar R usando el comando
runen gdb:
Obtener el programa usando
Rcpp::sourceCpp, y esperar a quegdbpause el programa una vez que alcance el punto de interrupción.
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ónY aquí hay un ejemplo usando
info locals,info args,list, yprint.
Finalmente, para salir del programa, escribe
exit(similar aq()en R.)