【问题标题】:Execution Flow - Possible to enter function twice before exiting?执行流程 - 可以在退出前两次进入函数吗?
【发布时间】:2014-06-05 23:04:24
【问题描述】:

我在日志文件中观察到一些我无法解释的

项目中的所有代码都是 ANSI C, 32bit exe 运行在 Windows 7 64bit 上

我有一个与此类似的工作函数,在单线程程序中运行,不使用递归。如图所示,在调试期间包含了日志记录:

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

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

我们偶尔会看到这样的日志条目:

时间/日期戳在左边,然后是message,最后一个字段是durationclock()调用之间的刻度到日志记录功能。此日志记录表明该函数在退出之前连续输入了两次。

在没有递归的情况下,在单线程程序中,执行流如何(或)在第一次调用完成之前两次进入函数?

编辑:(显示日志记录函数的顶部调用)

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

【问题讨论】:

  • 你如何确认只有一个线程?特别是,你怎么知道 UI 计时器没有创建单独的上下文来执行回调?
  • @jxh - 在我使用的环境中,有 UI 计时器,根据定义,它们在主线程中运行。还有其他选项,即创建自己的线程的 AsyncTimer,但在这种情况下,我只使用 UI 计时器。
  • 如果箭头是硬编码的,我不明白你可以得到谁Entering -&gt;Entering &lt;-
  • @ryyker:好的,但目前没有足够的证据可以提供帮助,AFAICS。如果代码真的是单线程的,并且日志功能真的很健全,并且真的没有其他代码可以输出到日志中,那么显然这不会发生(尽管有 UB)。所以充其量,我们只能推测。我认为你需要生成一个minimal test-case
  • 通过调用“WriteToLog()”调用的 MS Win 记录器有多种模式。如果实现了 EVENT_TRACE_FILE_MODE_CIRCULAR 模式,则 MS 文档指出“请注意,在多处理器计算机上,循环日志文件的内容可能会出现乱序。”此外,MS Doc 指出“EVENT_TRACE_NO_PER_PROCESSOR_BUFFERING”模式“使用此模式可以消除事件在使用系统时间在不同处理器上发布时出现乱序的问题。”

标签: c execution


【解决方案1】:

当时我问了这个问题,如果 C 标准中有一些晦涩的部分允许执行流在不先退出的情况下多次进入函数(鉴于不存在多线程或递归)

我相信你们的 cmets 已经清楚地回答了这个问题。借用@Oli Charlesworth 在一篇评论中所说的话,他总结得很好:

如果代码真的是单线程的,并且日志功能真的很健全,并且真的没有其他代码可以输出到日志中,那么显然这不会发生(尽管有 UB)。

但由于实际日志文件(由于专有原因我无法发布)多次证明了这种模式,@Oli Charlesworth 列出的条件之一实际上不适用于我们的软件。在这一点上我最好的猜测是,考虑到日志功能是 sane 并且是文件的唯一输入,考虑备用 context/Fiber @jxh 建议的可能性:

“仅主线程”可以表示多种含义。该库仍然可以在 POSIX 上使用 &lt;ucontext.h&gt;,或在 Windows 上使用 Fibers。

因此,我会将同样的问题发布给我的环境的供应商,特别是如果他们的 UI 计时器的运行方式允许由于光纤或线程而进行并行调用。

如果有人感兴趣,我也会用他们的回复更新这个答案。

编辑以显示结论:

事实证明,执行流程重复进入函数的原因是 隐式递归。也就是说,虽然工作函数没有显式地引用自己,但它被指定为两个独立事件生成器的事件处理程序。再加上对 Process System Events 的调用(我们环境中可用的一个函数,强制队列中的事件现在被处理)可以(并且确实)导致递归执行流到事件处理函数中。以下是一位对我们环境中的 UI 计时器和系统事件之间的关系有专门知识的人的引述:

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

【讨论】:

  • 很高兴您从库的实现方式的角度回答了您的问题。但是,请注意,由于您在问题中的问题陈述(并且您在此答案的第一段中提到它)明确排除了递归,因此没有人会给您这个答案。
  • @jxh - 我无意明确排除递归。我当时就知道我的代码中没有使用任何明显的递归语法。此外,在我发布此内容时,我不知道实际发生的事情可能发生。当有人解释我们库中的计时器回调实现是如何工作的时,我第一次提出了隐式递归的概念。您(和 Oli)是第一个针对您的 cmets 中的实际问题发表声明的人,我在帖子中引用了这些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-13
  • 2020-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多