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

时间:2021-10-05 03:28:03

I'm working on a project on an 8051 where every byte counts. As such, I am using some global variables where I normally wouldn't. The normal method of passing pointers into a function adds too much overhead here.

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

I have a number of functions that use single bit variables (a compiler specific extension to C) to signal the outcome of a function in addition to the normal return value.

我有许多函数使用单个位变量(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;

}

Given that the technique is evil, is there some way I can make the code clearer?

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

How best should I indicate which function calls will have the side-effect of setting these flags? Are comments enough? Should I name the functions to indicate their API (quasi-hungarian-style)? Should I use a macro to mark such calls:

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

#define FUNCTION_SETS_FLAGS(code) (code)

FUNCTION_SETS_FLAGS( c = A_Function() );

Any other ideas?

还有其他想法吗?

9 个解决方案

#1


Your globals are labelled for clarity, that's a good start.

你的全局变量标有清晰度,这是一个好的开始。

Ideally you want something that won't compile if you get it wrong. That means macros and comments won't work.

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

I'd stick to a naming convention for the functions - not necessarily Hungarian, but something like A_Function_Returns_Flags, or less verbose if you can think of that.

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

#2


Using a convention, whether you want to call it "Hungarian" or not, is the best way I can think to mark this offhand. Stylistically, some sort of naming prefix would be preferable over the empty #define, at least to me.

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

This is actually pretty common, I think. I know that the S60 programming environment uses a lot of conventional tags on functions to indicate that they throw exceptions, for example.

我认为这实际上很常见。据我所知,S60编程环境在函数上使用了很多传统的标签来表示它们会抛出异常。

#3


I did my Ph.D. on a similar issue in Java. I can tell you the one thing you shouldn't do: don't rely on the documentation because then you depend on someone actually reading it. You need to add some hint in the method name to indicate that the user should read the docs to learn about side effects. If you pick something and are consistent with it, you probably stand the most chance.

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

#4


If you just want to mention that a function affects global variable(s), then a simple (Hungarian) prefix might help.

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

But if you want to mention every single flag(s) that it affects, then, using the function header is probably the way to go. Like for example,

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

  /*************************************************************************
     * 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> 
  *************************************************************************/

#5


This doesn't really help you, but GCC has a way to do the opposite of what you want: to mark functions which have no side effects. See the const and pure attributes. This is more for optimization than documentation, thought: if the compiler knows that a given function does not examine any data other than its arguments, it can perform smarter optimizations such as loop-invariant code motion.

这并没有真正帮助你,但GCC有办法与你想要的相反:标记没有副作用的功能。请参阅const和pure属性。这更像是为了优化而不是文档,如果编译器知道给定的函数不检查除其参数之外的任何数据,它可以执行更智能的优化,例如循环不变的代码运动。

#6


You could use a macro to simulate the function to have more parameters:

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


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

I haven't tried to compile it, so cannot say that this will work, but I think it should.

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

#7


First I would try to code it in a way that there are only one producer and only one consumer for each of those flags. Then I would clear/set a flag only when needed. As for indicating the side-effect, A standard header on top of the function, doxygen style, should be enough:

首先,我将尝试以一种方式对其进行编码,即每个标志只有一个生产者和一个消费者。然后我会在需要时清除/设置一个标志。至于指示副作用,在函数顶部的标准头,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;
    }

On the other hand, if the error and ready flags are mutually exclusive, you could use a byte (or bits inside a byte/register) to indicate readiness or an error state.

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

0 for an error, 1 for not-ready/error-free and 2 for ready/error-free (or -1, 0, 1, whatever)

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

IIRC, the standard 8051 instruction set doesn't operate on single bits, so using a whole byte for (various) flags shouldn't give you a huge performance hit.

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

#8


If you haven't done so already, you might also want to check out the sdcc project on sourceforge, it's a C compiler specifically meant to be used for embedded development which also targets the 8051, in addition the compiler supports a number of custom, target-specific and non-standard compiler intrinsics for various use cases, also I have personally found the development team to be very open to and responsive about ideas for new enhancements and other related feature requests.

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

#9


If you really have to stick with these global variables, you can make it obvious that a function may modify them by expecting references to them as function arguments:

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

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

#1


Your globals are labelled for clarity, that's a good start.

你的全局变量标有清晰度,这是一个好的开始。

Ideally you want something that won't compile if you get it wrong. That means macros and comments won't work.

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

I'd stick to a naming convention for the functions - not necessarily Hungarian, but something like A_Function_Returns_Flags, or less verbose if you can think of that.

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

#2


Using a convention, whether you want to call it "Hungarian" or not, is the best way I can think to mark this offhand. Stylistically, some sort of naming prefix would be preferable over the empty #define, at least to me.

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

This is actually pretty common, I think. I know that the S60 programming environment uses a lot of conventional tags on functions to indicate that they throw exceptions, for example.

我认为这实际上很常见。据我所知,S60编程环境在函数上使用了很多传统的标签来表示它们会抛出异常。

#3


I did my Ph.D. on a similar issue in Java. I can tell you the one thing you shouldn't do: don't rely on the documentation because then you depend on someone actually reading it. You need to add some hint in the method name to indicate that the user should read the docs to learn about side effects. If you pick something and are consistent with it, you probably stand the most chance.

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

#4


If you just want to mention that a function affects global variable(s), then a simple (Hungarian) prefix might help.

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

But if you want to mention every single flag(s) that it affects, then, using the function header is probably the way to go. Like for example,

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

  /*************************************************************************
     * 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> 
  *************************************************************************/

#5


This doesn't really help you, but GCC has a way to do the opposite of what you want: to mark functions which have no side effects. See the const and pure attributes. This is more for optimization than documentation, thought: if the compiler knows that a given function does not examine any data other than its arguments, it can perform smarter optimizations such as loop-invariant code motion.

这并没有真正帮助你,但GCC有办法与你想要的相反:标记没有副作用的功能。请参阅const和pure属性。这更像是为了优化而不是文档,如果编译器知道给定的函数不检查除其参数之外的任何数据,它可以执行更智能的优化,例如循环不变的代码运动。

#6


You could use a macro to simulate the function to have more parameters:

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


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

I haven't tried to compile it, so cannot say that this will work, but I think it should.

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

#7


First I would try to code it in a way that there are only one producer and only one consumer for each of those flags. Then I would clear/set a flag only when needed. As for indicating the side-effect, A standard header on top of the function, doxygen style, should be enough:

首先,我将尝试以一种方式对其进行编码,即每个标志只有一个生产者和一个消费者。然后我会在需要时清除/设置一个标志。至于指示副作用,在函数顶部的标准头,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;
    }

On the other hand, if the error and ready flags are mutually exclusive, you could use a byte (or bits inside a byte/register) to indicate readiness or an error state.

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

0 for an error, 1 for not-ready/error-free and 2 for ready/error-free (or -1, 0, 1, whatever)

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

IIRC, the standard 8051 instruction set doesn't operate on single bits, so using a whole byte for (various) flags shouldn't give you a huge performance hit.

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

#8


If you haven't done so already, you might also want to check out the sdcc project on sourceforge, it's a C compiler specifically meant to be used for embedded development which also targets the 8051, in addition the compiler supports a number of custom, target-specific and non-standard compiler intrinsics for various use cases, also I have personally found the development team to be very open to and responsive about ideas for new enhancements and other related feature requests.

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

#9


If you really have to stick with these global variables, you can make it obvious that a function may modify them by expecting references to them as function arguments:

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

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