我在日志文件中发现了 ,我无法解释:
项目中的所有代码都是ANSI C,在Windows 7 64位上运行32位exe
我有一个与此类似的工作函数,在 单线程程序中运行,不使用递归 。在调试期间,包括如下所示的日志记录:
//This function is called from an event handler
//triggered by a UI timer similar in concept to
//C# `Timer.OnTick` or C++ Timer::OnTick
//with tick period set to a shorter duration
//than this worker function sometimes requires
int LoadState(int state)
{
WriteToLog("Entering ->"); //first call in
//...
//Some additional code - varies in execution time, but typically ~100ms.
//...
WriteToLog("Leaving <-");//second to last call out
return 0;
}
上述功能从我们的实际代码中简化,但足以说明问题。
我们偶尔会看到这样的日志条目:
如果时间/日期标记在左侧,那么消息,clock()
标记中的最后一个字段是持续时间在调用日志功能之间。此日志记录表示在退出之前已连续两次输入该函数。
如果没有递归,并且在单线程程序中,它是如何(或 )可能的,执行流可以在第一次调用完成之前两次输入函数?
编辑: (显示记录功能的最高呼叫)
int WriteToLog(char* str)
{
FILE* log;
char *tmStr;
ssize_t size;
char pn[MAX_PATHNAME_LEN];
char path[MAX_PATHNAME_LEN], base[50], ext[5];
char LocationKeep[MAX_PATHNAME_LEN];
static unsigned long long index = 0;
if(str)
{
if(FileExists(LOGFILE, &size))
{
strcpy(pn,LOGFILE);
ManageLogs(pn, LOGSIZE);
tmStr = calloc(25, sizeof(char));
log = fopen(LOGFILE, "a+");
if (log == NULL)
{
free(tmStr);
return -1;
}
//fprintf(log, "%10llu %s: %s - %d\n", index++, GetTimeString(tmStr), str, GetClockCycles());
fprintf(log, "%s: %s - %d\n", GetTimeString(tmStr), str, GetClockCycles());
//fprintf(log, "%s: %s\n", GetTimeString(tmStr), str);
fclose(log);
free(tmStr);
}
else
{
strcpy(LocationKeep, LOGFILE);
GetFileParts(LocationKeep, path, base, ext);
CheckAndOrCreateDirectories(path);
tmStr = calloc(25, sizeof(char));
log = fopen(LOGFILE, "a+");
if (log == NULL)
{
free(tmStr);
return -1;
}
fprintf(log, "%s: %s - %d\n", GetTimeString(tmStr), str, GetClockCycles());
//fprintf(log, "%s: %s\n", GetTimeString(tmStr), str);
fclose(log);
free(tmStr);
}
}
return 0;
}
答案 0 :(得分:1)
我问了当时想知道的问题 ,如果C标准中有一些不明确的部分允许执行流程在没有首先退出的情况下多次输入一个函数(鉴于不存在多线程或递归)
我相信你的评论清楚地回答了这个问题。借用@Oli Charlesworth在一篇评论中所说的话,他总结得非常好:
如果代码是真正的单线程,并且日志功能真正理智,并且真的没有其他代码可以输出到日志,那么显然这不可能发生(尽管有UB)。
但由于实际日志文件(由于专有原因我无法发布)已经证明了这种模式,@ Oli Charlesworth列出的条件之一对我们的软件来说并不是真的。我最好的猜测是,鉴于日志功能 sane 并且是文件的唯一输入,是考虑备用 context/Fiber @jxh建议的可能性:
“仅主要线程”可能意味着多件事。该库仍可能在POSIX上使用<ucontext.h>
,在Windows上使用Fibers。
因此,我会将同样的问题发布给我的环境供应商,特别是如果他们的UI计时器以允许由光纤或线程进行并行调用的方式运行。
如果有兴趣,我也会在回复时更新这个答案。
编辑以显示结论:
事实证明,执行流程重复输入函数的原因是 隐式递归 。也就是说,虽然worker函数没有明确引用它,但它被指定为两个独立事件生成器的事件处理程序。这与对过程系统事件的调用(我们的环境中可用的函数强制现在处理队列中的事件)一起可以(并且确实)导致递归执行流入事件处理函数。以下是在我们的环境中具有UI定时器和系统事件之间关系专业知识的人的引用:
“嵌套的定时器事件”确实等同于在离开之前两次进入函数的执行流程。基本上,它与基本递归相同:当你在一个函数内部时,你会调用相同的函数。这种情况和基本递归之间的唯一区别是递归调用是隐式的(通过ProcessSystemEvents)而不是显式的。但最终结果是一样的。“