条件跳转或移动取决于 for 循环中 strcat 的未初始化值

RAH*_*sen 0 c c++ malloc valgrind

我有一个包含 3 条染色体字符串的文件,我想将其连接成一个基因组。然后我必须跨多个线程访问这个串联字符串(我使用 pthread_t)。为此,我必须在提取数据时使用 pthread_mutex_lock,然后使用 strcat 连接使用 const char* 函数 fai_fetch 提取的数据,然后将数据保存为 char* (见下文)。

// genome_size the size of all the chromosomes together
// chr_total the number of chromosomes I wish to concatenate
char* genome = (char*) malloc(sizeof(char) * (genome_size+chr_total));

for (int i = 0; i < chr_total; i++){
    pthread_mutex_lock(&data_mutex);
    const char *data = fai_fetch(seq_ref,chr_names[i],&chr_sizes[i]);
    pthread_mutex_unlock(&data_mutex);
    //sprintf(&genome[strlen(genome)],data);
    strcat(genome,data);  
    //sprintf(genome+strlen(genome),data); //All three gives conditional jump or move error

    //sprintf(genome,data); // THIS SOLVES VALGRIND ISSUE ONE BUT DOES NOT GIVE A CONCATENATED CHAR*
}
Run Code Online (Sandbox Code Playgroud)

所有这些都有效,但是运行 valgrind 我得到

条件跳转或移动取决于引用“strcat(genome,data);”的未初始化值

未初始化的值是通过堆分配创建的,引用“char*genome=(char*)malloc(sizeof(char)*(genome_size+chr_total));”

根据其他 StackOverflow 答案,我尝试了 sprintf(&genome[strlen(genome)],data); 和 sprintf(基因组+strlen(基因组),数据); 而不是 strcat。然而,他们也给出了相同的 valgrind 消息。

唯一可以减轻此错误的方法是使用 sprintf(genome,data); 然而,这样我就不会得到完整的基因组,而只会得到一条染色体。

尝试基因组+= sprintf(基因组,数据); 给我 ./a.out': munmap_chunk(): 无效指针:和 ./a.out': free()

关于“未初始化的值是由堆分配创建的”错误 -> 那么我的问题是我只能在所有线程运行完毕后释放该内存。所以我不确定当我使用 malloc 时如何初始化堆分配中的值。

是否可以解决其中一些特定的 valgrind 错误?

dli*_*hen 5

使用Valgrind定位有问题的代码

条件跳转或移动取决于未初始化的值”消息意味着 Valgrind 已确定程序的某些结果取决于未初始化的内存。使用该--track-origins=yes标志来跟踪未初始化值的来源。它可能会帮助您找到该价值。从man 1 valgrind

当设置为 yes 时,Memcheck 会跟踪所有未初始化值的来源。然后,当报告未初始化值错误时,Memcheck 将尝试显示该值的来源。来源可以是以下四个位置之一:堆块、堆栈分配、客户端请求或其他各种来源(例如,对 brk 的调用)。

更具体地说,在您的程序中:

问题1:使用未初始化的genome

线路

char* genome = (char*) malloc(sizeof(char) * (genome_size+chr_total));
Run Code Online (Sandbox Code Playgroud)

genome使用分配缓冲区malloc(2),然后在以下位置消耗它:

strcat(genome,data);
Run Code Online (Sandbox Code Playgroud)

请注意,诸如strlen(3)和 之类的函数strcat(3)适用于 C 字符串,它们是以空字符 ('\0') 结尾的缓冲区。

malloc(2)只是分配内存并且不会初始化它,因此您分配的缓冲区可能包含任何值(并被视为未初始化)。您应该避免将字符串相关的函数与未初始化的缓冲区一起使用,因为这会导致未定义的行为。

幸运的calloc(2)是,这个技巧做到了——它分配缓冲区并将其所有位初始化为零,从而产生一个可以操作的有效的 0 长度 C 字符串。我建议进行以下修复以确保已genome初始化:

char* genome = (char*) malloc(sizeof(char) * (genome_size+chr_total));
Run Code Online (Sandbox Code Playgroud)

另请注意,我已添加了+1分配的缓冲区的长度。这样做是为了保证结果genome将以空终止符结束(假设这genome_size+chr_total是从返回的所有缓冲区的总大小fai_fetch)。

另请注意,就性能而言,calloc它比(因为它初始化数据)慢一点malloc,但在我看来,它更安全,因为它初始化整个缓冲区。出于您的特定程序的目的,您可以通过malloc仅使用和初始化第一个字节来节省性能负担:

char* genome = malloc(sizeof(char) * (genome_size + chr_total + 1));
if (NULL == genome) {
    perror("malloc of genome failed");
    exit(1);
}
// So it will be a valid 0 length c-string
genome[0] = 0;
Run Code Online (Sandbox Code Playgroud)

我们不必将最后一个字节初始化为 0,因为strcat它为我们写入了终止空字符。

(潜在)问题 2:使用潜在的非空终止datastrcat

正如您在问题中所描述的,fai_fetch提取一些数据:

strcat(genome,data);
Run Code Online (Sandbox Code Playgroud)

然后在行中消耗它strcat

char* genome = calloc(genome_size+chr_total+1, sizeof(char));
Run Code Online (Sandbox Code Playgroud)

正如我上面所写,因为您使用strcat,data也应该以空终止。

我不确定fai_fetch是如何实现的,但如果它返回一个有效的 C 字符串,那么一切都很好。

如果没有,那么您应该使用strncat它适用于非空终止的缓冲区。

man 3 strcat

strncat() 函数类似,不同之处在于

  • 它将使用 src 中的最多 n 个字节;和
  • 如果 src 包含 n 个或更多字节,则不需要以 null 终止。

我建议进行以下修复:

char* genome = malloc(sizeof(char) * (genome_size + chr_total + 1));
if (NULL == genome) {
    perror("malloc of genome failed");
    exit(1);
}
// So it will be a valid 0 length c-string
genome[0] = 0;
Run Code Online (Sandbox Code Playgroud)