【问题标题】:Does replacing implicit conversion with explicit casting have any side-effects?用显式转换替换隐式转换是否有任何副作用?
【发布时间】:2015-03-27 11:48:38
【问题描述】:

显式转换比只使用隐式转换更好吗?

例如,我有一个枚举...

/*This enum represents the various encryption types for wifi. For wifi capable devices, a bitwise & result of all supported encryption types should be returned.*/
typedef enum wifi_encryptionType {
    /*Unknown encryption - default value, and for if wifi standard is ever expanded.*/
    WIFIENCTYPE_UNKNOWN = 0,

    /*No encryption - an open network.*/
    WIFIENCTYPE_NONE = 1,

    /*WEP encryption - all widths.*/
    WIFIENCTYPE_WEP = 2,

    /*WPA 1 with a preshared key using Temporal Key Integrity Protocol.*/
    WIFIENCTYPE_WPA_PSK_TKIP = 4,

    /*WPA 1 with a preshared key using Advanced Encryption Standard via CCMP. */
    WIFIENCTYPE_WPA_PSK_AES = 8,

    /*WPA 2 with a preshared key using Temporal Key Integrity Protocol.*/
    WIFIENCTYPE_WPA2_PSK_TKIP = 16,

    /*WPA 2 with a preshared key using Advanced Encryption Standard via CCMP.*/
    WIFIENCTYPE_WPA2_PSK_AES = 32

} wifi_encryptionType;

我在结构中使用。

typedef struct {
    char ssid[32];
    wifi_encryptionType encryption;
    wifi_mode mode;
} WifiNetwork;

我使用该结构字段的值作为函数调用的参数...

read_uint8(readBuffer, &network.encryption);
//read_uint8 takes a struct pointer containing some buffer info, and a uint8_t pointer.

我收到警告。

warning: passing argument 2 of 'read_uint8' from incompatible pointer type
expected 'uint8_t *' but argument is of type 'enum wifi_encryptionType *'

我明白警告的含义。 “请注意,读取 uint8_t 并将其放入 wifi_encryptionType 字段可能会在其中放置不映射到您声明的任何值的值。”

类型转换现在隐式完成。

将其设为显式演员表会更好吗?明确演员阵容有什么好处 - 还是有任何缺点?

【问题讨论】:

  • 正如标签所指向的,没有“隐式转换”,只有“隐式转换”,即“显式转换” ”的定义。
  • @alk 是的,这是我的定义不匹配。我将代码视为“做”事情(它转换值)而不是让它发生(值以某种方式转换)。我的错。我修复了问题正文,并更新了问题标题以更具体地针对我的问题。

标签: c casting implicit-conversion


【解决方案1】:

这种情况下的警告不仅仅是编译器挑剔。这可能会中断。

原因是enum 类型的大小可能与uint8_t 不同。 C11 标准仅保证(第 6.7.2.2 节)

每个枚举类型都应兼容char,一个有符号整数类型, 或无符号整数类型。

如果你不走运并且enum 具有与例如相同的表示形式,例如int,那么您将有效地将指针传递给int 的初始字节。在大端系统上,该字节的值与int 不同,即使该值适合。

另一个问题(虽然在这里可能不适用)是严格别名,这意味着允许编译器假设相同的数据不会作为两种不同的类型访问。一个例子是让int*float* 指向同一个位置,通过int* 写入该位置,然后通过float* 从它读取。严格的别名规则允许代码优化,因为编译器可以假设通过int* 写入不会弄乱float* 指向的值(例如,它必须重新加载到寄存器中)。

严格别名在这里可能不适用的原因是uint8_t 在实践中几乎肯定是unsigned char,对此编译器会例外。 char*(或unsigned char*)可用于访问任何类型对象的内存。如果不允许这样做,那么就没有任何安全的方法可以根据需要进行“原始”字节操作,例如memcpy().

【讨论】:

  • 哦,字节序。 ...是的,这可能会搞砸一些事情。 ...这也很奇怪,在某些设备上工作而不在其他设备上工作。
  • @Pimgd:我在野外见过一些这样的错误,人们将指针传递给某种类型,将其转换为指向不同的、意外更大的类型的指针,然后分配给该类型通过指针,导致内存损坏。在这里,这种特殊情况不会成为问题,但也值得关注。最好的解决方案是显式创建正确类型的对象并将指针传递给该对象。 (附带说明一下,转换指针也可能会破坏严格的别名规则,尽管在这里它可能是安全的,因为 uint8_t 很可能是 char。)
  • 严格别名意味着允许编译器假设两个不同类型的指针在某些情况下不别名(指向同一个地方)顺便说一句。 (你不能“访问”相同的数据,因为两种不同的类型是另一种说法。)在某些情况下,它允许更有效的代码生成。不过,char*/unsigned char* 指针有一个例外——它们可以为其他指针起别名。 (否则,根据 memcpy() 的需要进行“原始”字节操作会很棘手。)
  • 也将其添加到答案中。 :P
  • 所以任何像这样的指针转换基本上都是坏消息,我应该修复它们,而不是将它们隐藏在显式转换之后。
【解决方案2】:

enum(最可能)是int

因此,您传递的是 int 的地址,而 uint8_t 的地址是预期的。

编译器会告诉你:

warning: passing argument 2 of 'read_uint8' from incompatible pointer type expected 'uint8_t *' but argument is of type 'enum wifi_encryptionType *'

这样做会导致灾难。

要解决这个问题,请使用临时中间变量:

{
  uint8_t tmp = network.encryption;
  read_uint8(readBuffer, &tmp);
  network.encryption = tmp;
}

【讨论】:

  • 为什么它是灾难的根源?你能举一个可能出错的例子吗?
  • enumuint8_t 的大小很可能不同,然后通过取消引用被调用函数内部的指针来访问变量的内存将失败。 @Pimgd
  • 当您将四字节 int 的地址转换为 uint8_t* 时,分配给 uint8_t* 只会更改 int 的一个字节,而保留三个字节不变,可能是垃圾。由于字节顺序,您不知道哪个字节被更改。传递 uint8_t 的地址并将其转换为 int* 会更糟;分配给 int* 将改变原来的 uint8_t 和内存中的三个字节。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
  • 2022-01-09
  • 2014-11-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-22
相关资源
最近更新 更多