【问题标题】:GCC: Casting const pointers to const pointer of array typedef with -Wcast-qual throws warningGCC:使用 -Wcast-qual 将 const 指针转换为数组 typedef 的 const 指针会引发警告
【发布时间】:2019-06-27 09:09:31
【问题描述】:

编辑:更深入地解释了问题here(谢谢@Eric Postpischil)。这似乎是 GCC 中的一个错误。

首先,让我从一些上下文开始:我正在编写的代码使用了一个我无法更改的 API,在一个我无法更改的 GCC 版本上,我不允许删除编译标志,以及当我完成它时,它必须精确为零警告或#pragmas。

编辑:也没有工会。

EDIT2:假设构建系统还使用 -Wall -ansi -pedantic 和阳光下的所有其他警告。 明天我会确认 GCC 版本,但我很确定它不会高于 GCC 7。与此同时,我正在使用 GCC 6.3 进行测试。

EDIT3:我将问题标记为“已回答”。为了完整起见,我在下面添加了更多信息:

我检查了正在使用的编译器版本,它并不漂亮。我们正在使用 Mingw,gcc.exe --version 告诉我它是 GCC 3.4.5。

此外,编译标志包括 wall wextra wcast-qual wpointer-arith wconversion wsign-conversion 以及与当前问题无关的其他标志。

问题

考虑以下代码:

#include "stdio.h"
#include "stdint.h"

typedef uint32_t MyType[4];

const MyType* foo(const uint8_t* a)
{
    return (const MyType*) a;
}

void myapi_foo(const MyType* d) {}

int main()
{
    uint8_t a[4*sizeof(uint32_t)];

    const MyType* b = foo((const uint8_t*) a);

    myapi_foo(b);

    return 0;
}

使用 GCC 和 -Wcast-qual 标志编译,此代码将引发以下警告:

警告:cast 丢弃来自指针目标类型的“const”限定符 [-Wcast-qual] return (const MyType*) a;

编辑:澄清一下,错误在这一行:

return (const MyType*) a;

问题原因

我知道问题的根本原因是 typedef 类型 MyType 实际上是一个数组。遗憾的是,我没有修改这个 typedef 的奢侈,也没有修改 API 函数myapi_foo 及其参数类型的可疑选择。 老实说,我真的不明白为什么编译器对这个演员阵容如此不满,所以澄清一下是非常受欢迎的。

问题

向编译器指示所有内容都应该被视为指向 const 数据的指针的最干净方式是什么?

废弃的和潜在的解决方案

以下是一些我发现但令我不满意的“解决方案”:

  • 删除 -Wcast-qual 标志。由于代码质量规则,我不能这样做。
  • 添加#pragma 以关闭该部分代码周围的警告(如here 所示)。同样,我也不能这样做。
  • 将指针转换为整数,然后再转换回指针(如图所示herereturn (const MyType*) (uint32_t) a;。这是非常粗略的,但是在这个项目中使用uint32_t 作为内存地址是有先例的,所以我可能不得不将它作为最后的努力。
  • 编辑:@bruno 建议使用联合来回避问题。这是一个便携且相当优雅的解决方案。但是,上述代码质量规则完全禁止使用联合。
  • 编辑:@Eric Postpischil 和@M.M 建议使用 (const void*) cast return (const void*) a;,无论sizeof(MyType*) 的值如何,它都可以工作。遗憾的是,它对目标不起作用。

感谢您的宝贵时间。

【问题讨论】:

  • 您应该指定您正在使用的 GCC 版本以及报告错误的行。
  • 如警告消息中所述,错误出现在 return (const MyType) a;* 我手头没有特定的 GCC 版本,但我可以重现GCC 6.3.0 的问题。明天我会用我可以分享的信息更新 OP。
  • return (const void *) a; 是否符合您的编码标准?
  • 我添加了另一个在 GCC 6.3 中有效的解决方法。

标签: c arrays gcc constants typedef


【解决方案1】:

你可以这样做:

const MyType* foo(const uint8_t* a)
{
    union {
      const uint8_t* a;
      const MyType* b;
    } v;

    v.a = a;
    return v.b;
}

w.c 是您修改后的文件:

pi@raspberrypi:/tmp $ gcc -pedantic -Wall -Wcast-qual w.c
pi@raspberrypi:/tmp $ 

无论编译器(没有 #pragma)或 int 和指针的相应大小(在 int 和指针之间没有强制转换),它都可以工作,但我不确定这是否非常优雅 ;-)

奇怪的是有那个foo函数同时用Wcast-qual编译,很矛盾


编辑,如果你不能使用 union 你也可以这样做

const MyType* foo(const uint8_t* a)
{
    const MyType* r;

    memcpy(&r, &a, sizeof(a));
    return r;
}

编译:

pi@raspberrypi:/tmp $ gcc -pedantic -Wall -Wcast-qual w.c
pi@raspberrypi:/tmp $ 

【讨论】:

  • 感谢您的回答。 foo 函数用于说明。当问题第一次出现时,演员就在对 myapi_foo 的调用中,我注意到使用联合的解决方案,但是我相当确定它们也被我们的编码规则禁止。跨度>
  • @A.Cadot 好的,您可以将我的 foo 定义用于 cast 而不会发出警告。禁止?我不做任何假设,当然 const uint8_t* 实际上是 const MyType*
  • 这是否仍然违反严格的别名规则?
  • 我必须使用的编码标准本质上是一个以安全为中心的 MISRA 版本。如果客户说不应使用联合,那么我宁愿在 ASM 中编写演员表,也不愿将联合放在我的代码中。就是这样的客户……
  • 需要注意的是,C 标准通常允许不同类型的指针具有不同的表示形式,因此不能保证通过联合或memcpy 将指针的字节重新解释为不同类型的指针按标准工作。当然,它会在许多实现中实现,但是,由于这个问题涉及一个严格的环境,在该环境中必须遵循比正常情况更详细的规则,因此应注意附加要求。
【解决方案2】:

这是GCC bug 81631。 GCC 无法识别到 const MyType * 的转换保留了 const 限定符。这可能是因为,在这个“指向四个const uint32_t的数组的指针”中,GCC执行了一个数组是否为const的测试,而不是数组元素是否为const

在某些 GCC 版本中,包括 8.2,解决方法是更改​​:

return (const MyType*) a;

到:

return (const void *) a;

可能在更多版本中起作用的更剧烈的变化是使用:

return (const MyType *) (uintptr_t) a;

关于转换和别名的注意事项:

此代码将a 传递给将其强制转换为const MyType * 的函数可能是个问题:

uint8_t a[4*sizeof(uint32_t)];

const MyType* b = foo((const uint8_t*) a);

在许多 C 实现中,MyTypeuint32_t 的数组,需要四字节对齐,但 a 只需要一字节对齐。根据 C 2018 6.3.2.3 6,如果 a 未正确对齐 MyType,则未定义转换结果。

此外,此代码建议uint_t 数组a 可用作四个uint32_t 的数组。这将违反 C 别名规则。您在问题中显示的代码似乎是示例,而不是实际代码,因此我们无法确定,但您应该考虑这一点。

【讨论】:

  • 这段代码确实是问题的非常简化的再现。本质上,API 使用 32 位字调用硬件,但应用程序使用更通用的 uint8_t* 方法。对混叠的担忧是相关的。在这种情况下,我未包含在示例保证a 中的架构和防御性代码可能不会错位(在此特定硬件上,错位访问也具有定义的行为,因此我们是三重安全的)。
  • 遗憾的是,这个解决方案似乎不适用于 GCC 6.3:main.c:30:12: warning: return discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] return (const void*) a; 我明天会看到这个解决方法是否适用于目标编译器。
  • @A.Cadot: Godbolt shows it does work in GCC 6.3. 你能重现 Godbolt 中的失败吗?
  • -pedantic 标志使其引发警告,即使在 Godbolt 上也是如此。话虽这么说,我不确定它是否是编译环境的一部分,我明天回到真正的目标时会知道。
【解决方案3】:

如果没有任何效果,您可能希望使用uintptr_t hammer,如果实现提供了它。根据 C11 标准,它是可选的:

const MyType* foo(const uint8_t* a)
{
    uintptr_t uip = (uintptr_t) a; 

    return (const MyType*) uip;
}

【讨论】:

  • 是的,这与我在 OP 中提到的解决方案之一非常接近(转换为 uint32_t,这是我必须使用的 uintptr_t 未定义)。文案其实不是必须的,可以使用双重强制转换:return (const MyType*) (uint32_t) a;
  • 好吧,unit32_t 也可能会出现警告,如果不是 32 位的话。
  • @A.Cadot:“因为 uintptr_t 未定义)”很有趣。请问你在哪个平台?这个事实也会让我想,为什么它丢失了...... - 在这个平台上将指针转换为整数是否有问题?
  • 它是嵌入式开发,标准头文件根本不可用。 uint32_t 等常见标准类型在自定义标头中重新定义,我相当确定 uintptr_t 不是其中之一。我可以将它重新定义为uint32_t(因为它是一个 32 位架构)但是这个项目已经有很多内存地址硬编码或存储为uint32_t(手动 MMU 配置等)所以没有必要添加typedef 只是为了删除警告。因此,“为什么”令人遗憾的是很无聊,“因为项目就是这样做的”。
  • 我还应该提到,就客户而言,1990 年之后的任何标准化都是异端。
猜你喜欢
  • 1970-01-01
  • 2017-05-15
  • 2013-10-20
  • 1970-01-01
  • 2015-09-10
  • 1970-01-01
  • 2016-07-03
  • 2010-12-14
  • 2021-12-31
相关资源
最近更新 更多