c-Elusive堆栈崩溃错误:为什么我的string_to_float函数有时会崩溃



我正在使用一些在运行arch linux的Raspberry Pi(我认为是3(上运行的遗留C代码。作为应用程序启动的一部分,它逐行读取文件,并将每一行存储到某个自定义结构中。

一百次中有99次,这种方法效果很好,从此我们都过着幸福的生活。在那个奇怪的输出中,我得到了一个堆栈粉碎检测到的错误,强制重置。

我已经验证了这些行是正确的并且格式一致。

我已经将其缩小到另一个在文件解析过程中被调用两次的函数:

const char* num = "+-.0123456789";
const char* Mults = "pnumkMGT";
float engtof(char* s)
{
printf("engtof: %sn",s);
float f, g = 1;
char m, *q = 0, w[32] = {0};
int i;
for(i = 0; i < 32; i++) w[i] = 0; // clear w
strcpy(w, s);
for(q = w; strchr(num, *q) != NULL; q++);
puts(q);       // sometimes prints some garbage after the desired char
if(*q == 'E' || *q =='e')
return atof(w);
if(*q != 0 && strchr(Mults, *q) != NULL)
{
m = *q;
g = getMult(m); // behavior already verified
*q = 0;
}
puts(w);       // seems to reach here before sometimes stack smashing, but w still looks right...
f = atof(w);
f *= g;
return f;
}

(忽略各种printf和put语句。这些语句是用来调试的,因为我们不知道如何从NetBeans 12.3进行远程调试(

这应该取一个工程格式的字符串(s="{value}{10的幂的乘数}}单位}"((例如,s="xkV"或s="a.bcmA"(,并将其转换为浮点值(在示例中为0002或0.00abc(。在启动期间,当一切正常时,它会被调用约300次,但当它不起作用时,我们会在第一次调用时堆栈smash(通常s="0"(。

到目前为止,我在堆栈粉碎方面发现的所有内容都涉及缓冲区溢出和超范围索引,但据我所知,这两种情况在这里都不适用。当然,我对指针的理解只是一般。。。

如果每次都发生这种情况,我相信我能找到问题,但由于这种情况很少发生,我不知道为什么会发生这种情况。有人对此有什么见解吗?

编辑:以下是在启动期间调用engtof的函数:

void loadCalFactors()
{
printf("loadCalFactorsn");
FILE *fp;
char s[120], w[80] = {0}, *p;
int i, j;
fp = fopen(fileCF, "r");
if(fp == NULL)
{
sprintf(s, "Can't open: %sn", fileCF);
printf(s);
netWrite(s);
return;
}
for(i = 0; i < N_CALFACTOR; i++)
{
j = 0;
fgets(s, 80, fp);
//puts(s);
p = strtok(s, ",");
while(p != NULL)
{
//puts(p);
switch(j)
{
case 0:
//puts("Header");
strcpy(w, p);// header
j++;
break;
case 1:
//puts("Gain");
calFactors[i].g = engtof(p);
j++;
break;
case 2:
//puts("Offset");
calFactors[i].os = engtof(p);
j++;
break;
case 3:
//puts("Unit");
strcpy(calFactors[i].unit, p);
j++;
break;
}
//printf("j= %i, p= %sn",j,p);
p = strtok(NULL, ",");
}
}
fclose(fp);
puts("loadCalFactors done");
}

它似乎只是在第一次打电话给engtof时出现了问题。如果它超过了这一点,那么它就可以顺利地通过文件。

以下是fileCF:的前几行

Input Source 5V Range,0,0,V
Input Voltmeter 5V Range,0,0,V
Input Source -5V Range,0,0,V
Input Voltmeter -5V Range,0,0,V
Input Source 50V Range,0,0,V
Input Voltmeter 50V Range,0,0,V
Input Source -50V Range,0,0,V
Input Voltmeter -50V Range,0,0,V

下面是getMult:

float getMult(char m)
{
puts("getMult");
float f;
printf("m=%cn",m);
switch(m)
{
case 'p': f = 1E-12; break;
case 'n': f = 1E-9; break;
case 'u': f = 1E-6; break;
case 'm': f = 1E-3; break;
case 'k': f = 1E3; break;
case 'M': f = 1E6; break;
case 'G': f = 1E9; break;
case 'T': f = 1E12; break;
default: f = 1; break;
}
//printf("f=%gn",f);
return f;
}

为了帮助生产MVE,以下是我们的所有产品:

#include <fcntl.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <time.h>
#include <wiringPi.h>
#include <math.h>
#include <ctype.h>

我甚至不知道在上面分享的部分中使用了多少,我相信没有一个被充分利用。

for(q = w; strchr(num, *q) != NULL; q++);

如果调用函数时s指向字符串"0",则会调用未定义的行为。只有当q指向不在num中的非空字符时,此循环才会终止,直到q指向w的边界之外时才会终止。

如果希望q指向w中不属于num的第一个字符,则可以使用以下行:

q = w + strspn( w, num );

也可以通过在循环中调用strchr来解决这个问题(效率较低(,正如您所尝试的那样。然而,正确的方法是:

for(q = w; *q != '' && strchr(num, *q) != NULL; q++)
;

添加表达式*q != ''将防止指针q超出w的边界(假设字符串具有空终止字符(。

最新更新