转换 return 类型的函数的 return 值

Cast the return value of a function that returns a casted type

我想从编译器的角度了解当一个转换类型被 returned 到一个函数时会发生什么,该函数也对 returned 值进行类型转换。我想知道显式类型转换 return 值的优点和缺点是什么。

static uint16_t sResult_U16;

static uint16_t GetMyResult(void) 
{
    return (uint16_t)sResult_U16;
}

static uint16_t GetMyResult(void) 
{
    return sResult_U16;
} 

如果 return 类型与函数类型不同,编译器会翻译什么?例如。

static int16_t sResult_S16;

static uint16_t GetMyResult(void) 
{
    return sResult_S16;
}

从编译器的角度来看,(uint16_t)sResult_U16; 是无意义的,因为 sResult_U16 的类型已经与 return 类型相同。在第一种情况下,它会简单地忽略无用的转换。

在第二种情况下,您使用的类型不同于 return 类型。然后将变量转换为与 return 类型相同的类型,这实际上是通过采用带符号变量的原始二进制表示并将其转换为原始二进制无符号等价物来完成的(详细的形式规则是引用在这个答案的底部)。


正式的细节可以在 C17 6.8.6.4/3 中找到,它说:

If the expression has a type different from the return type of the function in which it appears, the value is converted as if by assignment to an object having the return type of the function.

其中“如同通过赋值”是重要部分 - 它与使用 = 时的规则相同。所以我们要查找分配规则 6.5.15.1:

In simple assignment (=), the value of the right operand is converted to the type of the assignment expression and replaces the value stored in the object designated by the left operand.

此转换隐式发生,不需要强制转换 - 您的示例中的强制转换很混乱。

在特定情况下,发生从有符号到无符号的整数转换 (6.3.1.3):

Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.

return 语句就像赋值一样工作,这意味着

的行为
FOO bar(void) 
{
    ...
    return baz;
}

相同
FOO bar(void)
{
    ...
    FOO return_value;
    return_value = baz;
    return returnValue;
}

return baz; 仅当赋值 return_value = baz; 有效时才有效。

现在,对于赋值(return 也是如此),右操作数的值被转换为左操作数的值,因此如果您的函数 returns uint16_t,然后使用语言规则将 return 语句中表达式的值转换为 uint16_t

如果到 uint16_t 的转换在没有显式转换的情况下有效,对于整数和实数浮点类型也是如此,那么转换到 (uint16_t) 不会改变任何东西。但是,指针没有到整数的隐式转换,因此如果您尝试执行 ill-advised 事情并且 return 一个指针从这个函数强制到 uint16_t,你需要一个显式转换。

由上可知:

static int16_t sResult_S16;

static uint16_t GetMyResult(void) 
{
    return sResult_S16;
}

被组装,以便通过将有符号整数转换为对 65536 取模的正数来将其转换为无符号整数。实际上,由于您恰好使用了 int16_tuint16_t,转换结果将是在完全相同的位表示中,但可能 zero-extended.

我们可以将其分为三种情况:

  1. 如果 return 表达式与函数的 return 类型具有相同的类型,则它 return 不加改变地发送给调用者。
  2. 如果return表达式有不同的类型,但根据一定的约束相似,它会自动转换为函数的return类型。
  3. 如果 return 表达式的类型超出约束,编译器会报错。编译器可能会或可能不会继续编译程序,具体取决于多种因素。

这对于 return 表达式中的转换意味着:

  1. 转换为 return 类型的函数将无效,因为表达式已经是必要的类型。所以没必要。
  2. 强制转换为 return 类型的函数将与无论如何都会发生的自动转换具有相同的效果。所以没必要。
  3. 必须强制转换为 return 类型的函数才能避免编译器警告。这样的演员阵容是否正确取决于具体情况。有些类型转换已定义和需要的行为但不会自动执行,但也有类型转换可能具有未定义或不需要的行为。

C 2018 6.8.6 4 3 说“......如果表达式的类型不同于它出现在其中的函数的 return 类型,则该值就像通过赋值给具有函数的 return 类型。” C 6.5.16.1 1 规定了分配的约束条件。简化,他们允许:

  • 正在将数字类型分配给数字类型。
  • 正在将结构或联合类型分配给兼容的结构或联合类型。
  • 将指向类型的指针分配给指向至少具有相同限定符(constvolatilerestrict_Atomic)的兼容类型的指针.
  • 将指向 void 的指针分配给指向对象类型的指针或 vice-versa,至少使用相同的限定符。
  • 正在将空指针常量分配给指针。
  • 正在将指针分配给 _Bool

已定义但未自动执行的转换示例是将 int * 转换为 char *。如果声明为 return char * 的函数已经在 int *x 中计算了一些值并想要 return 它,它不能使用 return x; 因为这不满足任何上面列出的约束。但是(char *) x是有定义的(由C 2018 6.3.2.3 7),所以可以使用return (char *) x;

请注意,虽然编译器必须接受满足所列约束之一的 return 表达式,但它可能会产生警告。也就是说,它可以显示一条消息,警告用户可能有意想不到的事情,例如 returning a double 其中 return 类型是 int,因此可能会丢失信息而不会打算,但 C 标准要求编译器无论如何都要继续编译程序。