如何标记带副作用的代码?

时间:2009-04-13 21:59:27

标签: c embedded c-preprocessor 8051 side-effects

我正在研究8051上的每个字节都很重要的项目。因此,我正在使用一些全局变量,而我通常不会这样做。将指针传递给函数的常规方法在这里增加了太多的开销。

我有许多函数使用单个位变量( C 的编译器特定扩展)来指示函数的结果以及正常返回值。

bit global_error_flag = 0;
bit global_data_ready_flag = 0;

unsigned char A_Function (void) {
    // Do some stuff

    if ( badness ) {
        global_error_flag = 0;
        global_data_ready_flag = 1;

        return 0;
    }

    if ( data_is_ready_use ) {
        global_data_ready_flag = 1;
    }

    return a_value;    
}

void Other_Function (void) {
    unsigned char c;

    c = A_Function();

    if( global_error_flag) {
        // Do error stuff here.
    }
    else
    if( global_data_ready_flag ) {
        // Do data processing here.
    }
    global_error_flag = 0;
    global_data_ready_flag = 0;

}

鉴于该技术是邪恶的,有什么方法可以使代码更清晰?

我应该如何最好地指出哪些函数调用会产生设置这些标志的副作用?评论足够吗?我应该命名函数来表明他们的API(准匈牙利式)吗?我应该使用宏来标记此类呼叫:

#define FUNCTION_SETS_FLAGS(code) (code)

FUNCTION_SETS_FLAGS( c = A_Function() );

还有其他想法吗?

9 个答案:

答案 0 :(得分:6)

使用约定,无论你是否想要称它为“匈牙利语”,都是我能想到的最好的方法。在风格上,某种命名前缀优于空#define,至少对我而言。

我认为这实际上很常见。我知道S60编程环境在函数上使用了很多常规标记来表示它们会抛出异常,例如。

答案 1 :(得分:3)

为了清晰起见,你的全局标记已被标记,这是一个良好的开端。

理想情况下,如果你弄错了,你会想要一些无法编译的东西。这意味着宏和注释将无效。

我坚持使用函数的命名约定 - 不一定是匈牙利语,但是A_Function_Returns_Flags之类的东西,或者如果你能想到那么简单。

答案 2 :(得分:3)

我做了博士学位。关于Java中的类似问题。我可以告诉你一件事你不应该做的事情:不要依赖文档,因为那时你依赖于实际阅读它的人。您需要在方法名称中添加一些提示,以指示用户应阅读文档以了解副作用。如果你选择了一些与之相符的东西,你可能最有可能。

答案 3 :(得分:3)

如果你只是想提一个函数影响全局变量,那么一个简单的(匈牙利语)前缀可能会有所帮助。

但是如果你想提及它影响的每一个标志,那么,使用函数头可能是要走的路。例如,

  /*************************************************************************
     * FUNCTION    : <function_name>
     * DESCRIPTION : <function description> 
     * PARAMETERS  : 
     *  Param1  - <Parameter-1 explanation>
     *  Param2  - <Parameter-2 explanation>
     *  Param3  - <Parameter-3 explanation>
     * RETURN      : <Return value and type>
     * GLOBAL VARIABLES USED: 
     *  Global1 - <Global-1 explanation>
     *  Global2 - <Global-2 explanation>
     *  Global3 - <Global-3 explanation> 
  *************************************************************************/

答案 4 :(得分:2)

这对你没有什么帮助,但是GCC有办法做你想要的相反的:标记具有 no 副作用的函数。请参阅constpure属性。这更像是为了优化而不是文档,如果编译器知道给定的函数不检查除其参数之外的任何数据,它可以执行更智能的优化,例如loop-invariant code motion

答案 5 :(得分:2)

您可以使用宏来模拟函数以获得更多参数:


unsigned char _a_function(void);

#define A_Function(ret_val) (*(ret_val) = _a_function(), !global_error_flag)

...
unsigned char var;
/* call the function */
if (!A_Function(&var))
{
    /* error! */
}
else
{
    /* use var */
    var++;
}

我没有尝试编译它,所以不能说这会起作用,但我认为应该。

答案 6 :(得分:0)

首先,我会尝试以一种只有一个生产者和每个标志只有一个消费者的方式对其进行编码。 然后我会在需要时清除/设置一个标志。 至于指示副作用,函数顶部的标准头,doxygen样式,应该足够了:

    // Function func
    // Does something
    // Consumes ready_flag and  sets error_flag on error.

    int func()
    {
        if (ready_flag)
        {
            //do something then clear the flag
            if (some_error)
                error_flag = x;
            ready_flag = 0;
        }
        //don't mess with the flags outside of their 'scope'
        return 0;
    }

另一方面,如果错误和就绪标志是互斥的,则可以使用一个字节(或字节/寄存器中的位)来指示准备就绪或错误状态。

0表示错误,1表示未就绪/无错误,2表示就绪/无错误(或-1,0,1,无论如何)

IIRC,标准8051指令集不对单个位进行操作,因此对于(各种)标志使用整个字节不应该给您带来巨大的性能损失。

答案 7 :(得分:0)

如果你还没有这样做,你可能还想查看sdcc project on sourceforge,它是一个专门用于嵌入式开发的C编译器,也是针对8051的,另外编译器支持针对各种用例的自定义,目标特定和非标准编译器内在函数的数量,我个人也发现开发团队对新增强功能和其他相关功能请求的想法非常开放和响应。

答案 8 :(得分:-2)

如果你真的必须坚持使用这些全局变量,你可以明确表示函数可以通过期望将它们作为函数参数引用来修改它们:

unsigned char A_Function (bit *p_error_flag, bit *p_data_ready_flag)
{
  ...
}