2014-04-21 44 views
3

我观察到一个日志文件的东西,我无法解释:执行流程 - 可能在退出前两次输入功能?

项目的所有代码被ANSI C,32位exe文件在Windows 7 64位

运行我have类似这样的工人功能在一个单线程程序运行时,不使用递归。在调试过程中记录被包括,如下所示:

//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; 
} 

上面的函数是从我们的实际代码简化但是足以用于说明问题。

我们有时看到的日志条目,如本:

enter image description here

时间/日期邮票是在左边,然后消息,最后一个字段是时间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; 
} 
+4

你是如何确认只有一个线程的?特别是,你如何知道UI定时器没有创建一个单独的上下文来执行回调? – jxh

+0

@jxh - 在我使用的环境有定时器在主线程中运行的UI定时器,还有其他选项,例如AsyncTimer,它创建了自己的线程,但在这种情况下,我只使用UI定时器 – ryyker

+0

关于快速执行代码,有时候输出流可能会失序,在每次写入之后尝试fflush(stdout)(或者发送输出的时候),看看实际发生了什么。 –

回答

1

我问这个问题的时候想知道是否有C标准,允许的一些模糊的一部分执行流程进入一个函数不止一次在没有先退出(因为多线程或递归不在场)

您的意见,我相信,哈已经明确回答了这个问题。从什么@Oli查尔斯沃思在一个评论说借鉴,他概括起来相当不错:

如果代码确实是单线程的,和对数函数是真正理智的,而且也确实没有其他的代码可以输出到日志,那么显然这不会发生(UB尽管)。

但由于实际日志文件(我不能张贴特殊原因),在多个场合已证实了这一格局,@Oli查尔斯沃思列出的条件之一,实际上不是我们的软件如此。在这一点上考虑到日志功能是理智并且是唯一的输入到文件中,我最好的猜测,是考虑@jxh建议的替代context/Fiber可能性:

“主线程只有“可以表示多种事物。该库仍可能在POSIX上使用<ucontext.h>,或者在Windows上使用Fibers。

因此,我会将这个问题发布到我的环境的供应商,特别是如果他们的UI定时器以允许由于光纤或线程并行呼叫的方式运行。

如果有兴趣,我也会更新这个答案与他们的回应。

编辑,显示的结论:

事实证明,执行流程的双重进入一个函数的原因是隐含的递归。也就是说,虽然worker函数没有明确引用它,但,它被指定为两个独立事件生成器的事件处理程序。加上过程系统事件(在我们的环境中可用的函数迫使队列中的事件现在处理),可以(并且确实)导致递归执行流进入事件处理函数。以下是在我们的环境中具有UI定时器和系统事件之间关系专业知识的人员的引用:

“定时器事件嵌套”的作用与执行流程在离开前两次进入函数等同。基本上,它和基本的递归是一样的:当你在一个函数中时,你调用了相同的函数。这种情况与基本递归的唯一区别在于递归调用是隐式的(通过ProcessSystemEvents)而不是显式的。但最终结果是一样的。“

+0

很高兴你从图书馆实施的角度回答了你的问题。但是,请注意,由于您在问题中的问题陈述(以及您在本答案的第一段中提到的问题)明确排除了递归,所以没有人会以此为答案。 – jxh

+1

@jxh - 这不是我特意排除递归的意图。我当时知道我没有在我的代码中使用任何可以递归的语法。另外,在我发布这篇文章的时候,我不知道实际发生的事情是否会发生。当有人解释我们库中的定时器回调实现如何工作时,_implicit递归_的概念首次呈现给我。你(和奥利)是第一个发表声明的人,他们在你的评论中发现了真正的问题,我在文章中引用了这些内容。 – ryyker