在R中调用C函数trimCovar时反复出现堆损坏(Heap Corruption)错误的排查求助
Update Notes
- UPDATE3: 问题已解决,保留原始代码供参考——我已在下方发布了包含最终代码的回答,方便大家查看最终实现。
- UPDATE2: 重构代码,使用
R_alloc替代calloc以实现自动内存清理,但问题仍未解决。 - UPDATE: 如果在
UNPROTECT(1)前添加该行代码:Rprintf("%p %p %p", (void *)rans, (void *)fm, (void *)corrs);,函数执行时就不会出现堆损坏错误。推测可能是后台垃圾回收调用在执行完成前损坏了某个指针,导致写入无效指针。需要注意的是,如果不打印这三个指针地址,错误就会重现。另外,我在M1 Mac上运行代码,通过R CMD SHLIB使用clang编译,Apple硅芯片可能是问题诱因。
I'm stuck debugging a heap corruption issue and hoping the community can help out. I've written a C function trimCovar to optimize part of my R code, but repeated calls to this function from R trigger a heap corruption error. The function is invoked via .Call("trimCovar", ...).
Debugging Challenges
- I'm on macOS, so I can't use Valgrind for memory debugging.
- The C function relies on inputs from R, making it impossible to debug in isolation.
- The heap corruption only occurs when the C function is called 10+ times within an R function—direct repeated
.Callcalls don't trigger the error. - The error doesn't occur at a fixed location in the code.
Function Context & Goal
I'm starting with two sets of vectors, which I condense into a frequency matrix: each column corresponds to a position in the vector sets, each row corresponds to a specific character value. To simplify preprocessing, I pass a combined frequency matrix for both vector sets. Here's an example of what that looks like:
Input Vectors:
v1_1 = 101, v1_2 = 011
v2_1 = 111, v2_2 = 110Frequency Matrix:
position: | 1_1 | 1_2 | 1_3 | 2_1 | 2_2 | 2_3 |
0: 0.5 0.5 0.0 0.0 0.0 0.5
1: 0.5 0.5 1.0 1.0 1.0 0.5
The goal of trimCovar is to find the NV positions with the highest correlation between the two vector sets, calculated using pairwise KL divergence. Results are stored in an ascending-sorted linked list, and I only need to return the top NV entries' positions as a vector (duplicates allowed)—R handles the rest of the logic.
Function Parameters
fMAT: Combined frequency matrix (RObject, passed as a flat vector)fSP: Column indices in the matrix corresponding to positions in the first vector setsSP: Column indices in the matrix corresponding to positions in the second vector setNV: Number of top correlated positions to returnNR: Number of rows infMAT
Error Message
When the error triggers, I see this output:
R(95564,0x104858580) malloc: Heap corruption detected, free list is damaged at 0x600000f10040 *** Incorrect guard value: 4626885667169763328 R(95564,0x104858580) malloc: *** set a breakpoint in malloc_error_break to debug
Additional Observations
- I suspect a dangling pointer is causing invalid memory references, but I can't pinpoint it.
- Running
gc()immediately after each call to the C function doesn't fix the issue. - Using lldb to debug, but I'm not familiar enough with it to get meaningful insights quickly.
- Adding debug prints shows the error usually occurs in the main nested loop, but the trigger point isn't consistent.
- I've saved the exact inputs that trigger the error, but running them in isolation doesn't reproduce the issue—only after repeated calls.
Code Implementation
#include <R.h> #include <Rdefines.h> #include <Rinternals.h> #include <math.h> #include <stdlib.h> #include <stdbool.h> typedef struct node { double data; int i1; int i2; struct node *next; } node; // Linked list // data is the correlation value, // i1 the position from first vector set, // i2 the position from second vector set node *makeNewNode(double data, int i1, int i2){ node *newNode; newNode = (node *)R_alloc(1, sizeof(node)); newNode->data = data; newNode->i1 = i1; newNode->i2 = i2; newNode->next = NULL; return(newNode); } //insert link in sorted order (ascending) void insertSorted(node **head, node *toInsert, int maxSize) { int ctr = 0; if ((*head) == NULL || (*head)->data >= toInsert->data){ toInsert->next = *head; *head = toInsert; } else { node *temp = *head; while (temp->next != NULL && temp->next->data < toInsert->data){ temp = temp->next; if (ctr == maxSize){ // Performance optimization, if we aren't inserting in the first NR // positions then we can just skip since we only care about the NR // lowest scores overall return; } ctr += 1; } toInsert->next = temp->next; temp->next = toInsert; } } // MAIN FUNCTION CALLED FROM R // (This is the one that crashes) SEXP trimCovar(SEXP fMAT, SEXP fSP, SEXP sSP, SEXP NV, SEXP NR){ // Converting input SEXPs into C-compatible values int nv = asInteger(NV); int nr = asInteger(NR); int sp1l = length(fSP); int sp2l = length(sSP); int *firstSeqPos = INTEGER(coerceVector(fSP, INTSXP)); int *secondSeqPos = INTEGER(coerceVector(sSP, INTSXP)); double *fm = REAL(fMAT); int colv1, colv2; // Using a linked list for efficient insert node *corrs = NULL; int cv1, cv2; double p1, p2, score=0; // USUALLY FAILS IN THIS LOOP for ( int i=0; i<sp1l; i++ ){ cv1 = firstSeqPos[i]; colv1 = (cv1 - 1) * nr; for ( int j=0; j<sp2l; j++ ){ cv2 = secondSeqPos[j]; colv2 = (cv2 - 1) * nr; // KL Divergence score = 0; for ( int k=0; k<nr; k++){ p1 = fm[colv1 + k]; p2 = fm[colv2 + k]; if (p1 != 0 && p2 != 0){ score += p1 * log(p1 / p2); } } // Add result into LL node *newNode = makeNewNode(score, cv1, cv2); insertSorted(&corrs, newNode, nv); } R_CheckUserInterrupt(); } SEXP ans; PROTECT(ans = allocVector(INTSXP, 2*nv)); int *rans = INTEGER(ans); int ctr=0; int pos1, pos2; node *ptr = corrs; for ( int i=0; i<nv; i++){ rans[2*i] = ptr->i1; rans[2*i+1] = ptr->i2; ptr = ptr->next; } UNPROTECT(1); return(ans); }
I've only allocated linked list nodes, and I thought R_alloc would handle automatic cleanup. I've double-checked input bounds—99.99% sure I'm not accessing firstSeqPos, secondSeqPos, ans, or fm out of bounds. I've also reviewed the R code that calls this function repeatedly and don't see issues there. It's been a while since I wrote C, so I'm probably missing something obvious. I'd prefer not to switch to a Linux machine for Valgrind unless I have to—hoping there's another way to track this down. Thanks in advance for any help!
内容的提问来源于stack exchange,提问作者Aidan Lakshman

