C 语言:从两个具有不同 return 值的相似函数中创建一个通用函数
C-languge: Creating a generic function out of two similiar functions which have different return values
我正在用两种不同的参数数据类型编写两个不同的函数。此外,这两个函数都有不同的 return 类型。第一个函数 Function_One_u4
有两个 uint8
类型的参数:signal_one_u4
和 signal_two_u4
。另一方面,函数 Function_Two_u16
具有相似的模式,但两个信号的类型为 uint16
:signal_one_u16
和 signal_two_u16
。因此,第一个和第二个函数分别具有 return 类型 uint8
和 uint16
。此外,两个函数 return default
语句中的不同 ERROR 值。
uint8 Function_One_u4(const uint8 mode_u2,
const uint8 signal_one_u4,
const uint8 signal_two_u4)
{
switch(mode_u2)
{
case NOT_ACTIVE_U2:
{
return signal_two_u4;
}
case ACTIVE_U2:
{
return signal_one_u4;
}
case ERROR_U2:
{
return signal_one_u4;
}
case NOT_AVLB_U2:
{
return signal_two_u4;
}
default:
{
return ERROR_U4; /* Return value of 15 */
}
}
}
uint16 Function_Two_u16(const uint8 mode_u2,
const uint8 signal_one_u16,
const uint8 signal_two_u16)
{
switch(mode_u2)
{
case NOT_ACTIVE_U2:
{
return signal_two_u16;
}
case ACTIVE_U2:
{
return signal_one_u16;
}
case ERROR_U2:
{
return signal_one_u16;
}
case NOT_AVLB_U2:
{
return signal_two_u16;
}
default:
{
return ERROR_U16; /* Return value of 65535 */
}
}
}
void main(void)
{
uint8 ret_val_u4 = Function_One_u4();
uint16 ret_val_u16 = Function_Two_u16();
}
您会注意到函数几乎具有相同的逻辑 - 基于参数 uint8 mode_u2
,它们 return 要么是第一个信号,要么是第二个信号。因此,在 templates 的帮助下制作一个泛型函数是有意义的。通用函数将避免重复 switch case
代码:
<T> Generic_Function_<T> (const uint8 mode_u2,
const <T> signal_one,
const <T> signal_two,
const <T> error)
{
switch(mode_u2)
{
case NOT_ACTIVE_<T>:
{
return signal_two;
}
case ACTIVE_<T>:
{
return signal_one;
}
case ERROR_<T>:
{
return signal_one;
}
case NOT_AVLB_<T>:
{
return signal_two;
}
default:
{
return error;
}
}
}
uint8 Function_One_u4(const uint8 mode_u2,
const uint8 signal_one_u4,
const uint8 signal_two_u4)
{
Generic_Function_<T>(mode_u2, signal_one_u4, signal_two_u4);
}
uint16 Function_Two_u16(const uint8 mode_u2,
const uint8 signal_one_u16,
const uint8 signal_two_u16)
{
Generic_Function_<T>(mode_u2, signal_one_u16, signal_two_u16);
}
但是C语言不支持模板。我在网上发现可以使用预处理器宏创建 C 模板。但我也读到应该避免对函数使用宏,因为它们增加了在代码中引入错误的机会。
我正在用 C 语言编写一个安全关键的 MISRA 软件,所以代码中潜伏的意外错误并不是一件好事:)。
对于如何处理代码重复,还有其他建议吗?
提前谢谢您!
你已经涵盖了一切。您可以编写一个宏来为您生成函数,也可以复制新类型的逻辑。 C11 鼓励通过 _Generic
preprocessor directive.
使用宏进行泛型编程
_Generic
被 MISRA 禁止,类似函数的宏也是如此。所以在 MISRA 下你需要同时使用这两个函数。
一种方法是在任何地方只使用 Function_Two_u16 函数,并将错误值作为第三个参数,如下所示。
uint16 Function_Two_u16(const uint8 mode_u2,
const uint16 signal_one_u16,
const uint16 signal_two_u16, const uint16 error)
这个 Function_Two_u16 也适用于 uint8 值。
通常,程序员在避免代码重复的尝试中倾向于过于极端。当然,应该尽可能避免,但不要不惜任何代价。在安全关键系统的上下文中,应避免泛型编程:
首先,你至少要在理论上能够证明所有的执行路径都是真实执行的——代码覆盖率。泛型编程可以在此处添加虚假安全性,如果您将 2 个函数合并为一个函数,那么该函数确实会执行,但仅适用于其中一个用例。
其次,安全关键系统必须是 100% 确定性的,没有未知参数。所以传统的泛型编程从一开始就没有多大意义。理想情况下,您应该能够将规范中的每个要求跟踪到特定的代码块,然后为该代码设计测试。使用专用函数而不是通用函数,这部分变得更加容易。
这里可以使用宏或 C11 _Generic
(被 MISRA-C 禁止)的一些技巧,但我认为可以简化这个特定案例而不会使事情变得太复杂。为什么不简单地为“U 型”添加一个附加参数?我看不出为什么不能像这样重写函数的明显原因:
uint16_t function (uint8_t mode
uint8_t singal_one,
uint8_t signal_two
utype_t utype)
{
uint16_t result;
switch(mode)
{
case NOT_ACTIVE: { result = signal_two; break; }
case ACTIVE: { result = signal_one; break; }
case ERROR: { result = signal_one; break; }
case NOT_AVLB: { result = signal_two; break; }
default:
{
if(utype==U2)
{
result = (uint16_t) ERROR_U2;
}
else
{
result = ERROR_U4;
}
}
}
return result;
}
我从参数中删除了 const
,因为那部分只是与安全无关的主观编码风格。
来自 MISRA-C 上下文的注释:
- 我从 stdint.h 切换到推荐类型而不是自制类型。
- 从
uint8_t
到uint16_t
的转换在MISRA-C中没问题,它们属于相同的基本类型类别。您只需要避开关于将复合表达式分配给更宽类型的愚蠢规则,因此上面的复杂默认设置假设 ERROR_U2
是 8 位,但 ERROR_U4
是 16 位。
- 不允许使用多个 return 语句。这有时是一个糟糕的规则,有时却很有意义。在这种情况下,您可以通过使用结果变量和单个 return 来简单地符合要求,而不会影响可读性。
我没有在静态分析中运行这段代码,但我认为它符合 MISRA-C。
仅供参考,从一般情况和 MISRA-C 的角度来看都值得怀疑的宏替代方案将如下所示:
#define function(n) uint16_t function_##n (uint8_t mode_u2, ...
...
default: { result = ERROR_##n;
然后你可以让这个宏用 function(U2)
和 function(U4)
生成 2 个不同的函数,并让宏将函数的内容扩展到不同的枚举等。不解决 return 键入虽然,所以你可能仍然需要像我的第一个例子那样做。
只需实现一次逻辑,然后根据需要调用其他函数。
uint16 Function_Two_u16(const uint8 mode_u2,
const uint16 signal_one_u16,
const uint16 signal_two_u16)
{
switch(mode_u2)
{
case NOT_ACTIVE_U2:
{
return signal_two_u16;
}
case ACTIVE_U2:
{
return signal_one_u16;
}
case ERROR_U2:
{
return signal_one_u16;
}
case NOT_AVLB_U2:
{
return signal_two_u16;
}
default:
{
return ERROR_U16; /* Return value of 65535 */
}
}
}
uint8 Function_One_u4(const uint8 mode_u2,
const uint8 signal_one_u4,
const uint8 signal_two_u4)
{
uint8 ret = ERROR_U4;
uint16 val = Function_Two_u16(mode_u2, signal_one_u4, signal_two_u4);
if (val != ERROR_U16)
{
ret = (uint8)val;
}
return ret;
}
我正在用两种不同的参数数据类型编写两个不同的函数。此外,这两个函数都有不同的 return 类型。第一个函数 Function_One_u4
有两个 uint8
类型的参数:signal_one_u4
和 signal_two_u4
。另一方面,函数 Function_Two_u16
具有相似的模式,但两个信号的类型为 uint16
:signal_one_u16
和 signal_two_u16
。因此,第一个和第二个函数分别具有 return 类型 uint8
和 uint16
。此外,两个函数 return default
语句中的不同 ERROR 值。
uint8 Function_One_u4(const uint8 mode_u2,
const uint8 signal_one_u4,
const uint8 signal_two_u4)
{
switch(mode_u2)
{
case NOT_ACTIVE_U2:
{
return signal_two_u4;
}
case ACTIVE_U2:
{
return signal_one_u4;
}
case ERROR_U2:
{
return signal_one_u4;
}
case NOT_AVLB_U2:
{
return signal_two_u4;
}
default:
{
return ERROR_U4; /* Return value of 15 */
}
}
}
uint16 Function_Two_u16(const uint8 mode_u2,
const uint8 signal_one_u16,
const uint8 signal_two_u16)
{
switch(mode_u2)
{
case NOT_ACTIVE_U2:
{
return signal_two_u16;
}
case ACTIVE_U2:
{
return signal_one_u16;
}
case ERROR_U2:
{
return signal_one_u16;
}
case NOT_AVLB_U2:
{
return signal_two_u16;
}
default:
{
return ERROR_U16; /* Return value of 65535 */
}
}
}
void main(void)
{
uint8 ret_val_u4 = Function_One_u4();
uint16 ret_val_u16 = Function_Two_u16();
}
您会注意到函数几乎具有相同的逻辑 - 基于参数 uint8 mode_u2
,它们 return 要么是第一个信号,要么是第二个信号。因此,在 templates 的帮助下制作一个泛型函数是有意义的。通用函数将避免重复 switch case
代码:
<T> Generic_Function_<T> (const uint8 mode_u2,
const <T> signal_one,
const <T> signal_two,
const <T> error)
{
switch(mode_u2)
{
case NOT_ACTIVE_<T>:
{
return signal_two;
}
case ACTIVE_<T>:
{
return signal_one;
}
case ERROR_<T>:
{
return signal_one;
}
case NOT_AVLB_<T>:
{
return signal_two;
}
default:
{
return error;
}
}
}
uint8 Function_One_u4(const uint8 mode_u2,
const uint8 signal_one_u4,
const uint8 signal_two_u4)
{
Generic_Function_<T>(mode_u2, signal_one_u4, signal_two_u4);
}
uint16 Function_Two_u16(const uint8 mode_u2,
const uint8 signal_one_u16,
const uint8 signal_two_u16)
{
Generic_Function_<T>(mode_u2, signal_one_u16, signal_two_u16);
}
但是C语言不支持模板。我在网上发现可以使用预处理器宏创建 C 模板。但我也读到应该避免对函数使用宏,因为它们增加了在代码中引入错误的机会。 我正在用 C 语言编写一个安全关键的 MISRA 软件,所以代码中潜伏的意外错误并不是一件好事:)。
对于如何处理代码重复,还有其他建议吗? 提前谢谢您!
你已经涵盖了一切。您可以编写一个宏来为您生成函数,也可以复制新类型的逻辑。 C11 鼓励通过 _Generic
preprocessor directive.
_Generic
被 MISRA 禁止,类似函数的宏也是如此。所以在 MISRA 下你需要同时使用这两个函数。
一种方法是在任何地方只使用 Function_Two_u16 函数,并将错误值作为第三个参数,如下所示。
uint16 Function_Two_u16(const uint8 mode_u2,
const uint16 signal_one_u16,
const uint16 signal_two_u16, const uint16 error)
这个 Function_Two_u16 也适用于 uint8 值。
通常,程序员在避免代码重复的尝试中倾向于过于极端。当然,应该尽可能避免,但不要不惜任何代价。在安全关键系统的上下文中,应避免泛型编程:
首先,你至少要在理论上能够证明所有的执行路径都是真实执行的——代码覆盖率。泛型编程可以在此处添加虚假安全性,如果您将 2 个函数合并为一个函数,那么该函数确实会执行,但仅适用于其中一个用例。
其次,安全关键系统必须是 100% 确定性的,没有未知参数。所以传统的泛型编程从一开始就没有多大意义。理想情况下,您应该能够将规范中的每个要求跟踪到特定的代码块,然后为该代码设计测试。使用专用函数而不是通用函数,这部分变得更加容易。
这里可以使用宏或 C11 _Generic
(被 MISRA-C 禁止)的一些技巧,但我认为可以简化这个特定案例而不会使事情变得太复杂。为什么不简单地为“U 型”添加一个附加参数?我看不出为什么不能像这样重写函数的明显原因:
uint16_t function (uint8_t mode
uint8_t singal_one,
uint8_t signal_two
utype_t utype)
{
uint16_t result;
switch(mode)
{
case NOT_ACTIVE: { result = signal_two; break; }
case ACTIVE: { result = signal_one; break; }
case ERROR: { result = signal_one; break; }
case NOT_AVLB: { result = signal_two; break; }
default:
{
if(utype==U2)
{
result = (uint16_t) ERROR_U2;
}
else
{
result = ERROR_U4;
}
}
}
return result;
}
我从参数中删除了 const
,因为那部分只是与安全无关的主观编码风格。
来自 MISRA-C 上下文的注释:
- 我从 stdint.h 切换到推荐类型而不是自制类型。
- 从
uint8_t
到uint16_t
的转换在MISRA-C中没问题,它们属于相同的基本类型类别。您只需要避开关于将复合表达式分配给更宽类型的愚蠢规则,因此上面的复杂默认设置假设ERROR_U2
是 8 位,但ERROR_U4
是 16 位。 - 不允许使用多个 return 语句。这有时是一个糟糕的规则,有时却很有意义。在这种情况下,您可以通过使用结果变量和单个 return 来简单地符合要求,而不会影响可读性。
我没有在静态分析中运行这段代码,但我认为它符合 MISRA-C。
仅供参考,从一般情况和 MISRA-C 的角度来看都值得怀疑的宏替代方案将如下所示:
#define function(n) uint16_t function_##n (uint8_t mode_u2, ...
...
default: { result = ERROR_##n;
然后你可以让这个宏用 function(U2)
和 function(U4)
生成 2 个不同的函数,并让宏将函数的内容扩展到不同的枚举等。不解决 return 键入虽然,所以你可能仍然需要像我的第一个例子那样做。
只需实现一次逻辑,然后根据需要调用其他函数。
uint16 Function_Two_u16(const uint8 mode_u2,
const uint16 signal_one_u16,
const uint16 signal_two_u16)
{
switch(mode_u2)
{
case NOT_ACTIVE_U2:
{
return signal_two_u16;
}
case ACTIVE_U2:
{
return signal_one_u16;
}
case ERROR_U2:
{
return signal_one_u16;
}
case NOT_AVLB_U2:
{
return signal_two_u16;
}
default:
{
return ERROR_U16; /* Return value of 65535 */
}
}
}
uint8 Function_One_u4(const uint8 mode_u2,
const uint8 signal_one_u4,
const uint8 signal_two_u4)
{
uint8 ret = ERROR_U4;
uint16 val = Function_Two_u16(mode_u2, signal_one_u4, signal_two_u4);
if (val != ERROR_U16)
{
ret = (uint8)val;
}
return ret;
}