【问题标题】:Is there a GCC warning that can indicate any type change?是否有可以指示任何类型更改的 GCC 警告?
【发布时间】:2020-07-16 14:33:48
【问题描述】:

我的代码中有以下 typedef:

#define ROLLOVERDETECTION_OFF 0
#define ROLLOVERDETECTION_ON 1
typedef uint8_t rolloverdetection;

#define ROLLOVERDETECTED_NO  0
#define ROLLOVERDETECTED_YES 1
#define ROLLOVERDETECTED_UNKNOWN 255
typedef uint8_t rolloverdetected;

这两种类型,虽然它们具有相同的底层类型,但它们携带的信息并不相同。

实际上,如果代码中的任何人执行以下操作,我想收到警告:

rolloverdetection detection = ROLLOVERDETECTION_OFF;

// detection is set somewhere else in another function

void get_rolloverdetected(rolloverdetected *outvar)
{
    *outvar = detection; // this actually would return the setting of the detection rather than the detection itself
}

我在 gcc 中找不到任何此类警告选项,我遇到了 -Wconversion,但只有在有可能丢失信息的情况下才会发出警告,而我的示例中并非如此。

有人知道我能做些什么吗?显然,当真正需要更改类型时,应该可以进行强制转换。

【问题讨论】:

  • 为了这个问题,您可以为这些类型赋予更易区分的名称,例如 foobar
  • 由于 typedef 只是一个别名,如果有这个选项,我会感到惊讶。编译器只需要解析别名而不关心之后使用的名称。
  • 搜索 strong typedef。它已经讨论过很多次了。但基本上,您希望将基本类型包装在 structs 中,以传达其含义并可用于确保类型安全。另外,请只返回值而不是通过参数指针设置输出。
  • 所以rolloverdetection detection只能由函数设置,不能作为语句设置。然后只导出函数,不导出变量。
  • 这是 c 语言中真正的好东西之一。 typedef 没有定义类型。相反,它会创建一个别名。所以你应该改用“using”关键字,这样会更清楚。 :-) 要实现你想要的,你必须使用一个包含你的底层类型的结构来创建一个类型。

标签: c gcc


【解决方案1】:

简短的回答是不,你不能这样做。 typedef 声明一个别名,而不是一个新类型,因此任何遵守标准的编译器都无法拥有您现在想要的功能。

但是,您可以通过引入新类型、使用枚举或结构来实现它。

如果您使用 C,您将能够轻松地从一个枚举转换到另一个枚举。

因为一个结构的第一个元素的地址也是一个结构的地址,你可以将它从 int8 或另一个结构转换为新型。 (*((dest_type *)&value))

【讨论】:

  • 我不明白你对结构的意思。您不能将一个结构转换为另一个结构,即使它们具有相同的定义。 aa = (struct a)bb 不起作用。你是在建议像aa = *(struct a *)&bb 这样的东西吗?
  • 哦,是的,我的意思是,我应该准确地说您可以通过转换地址而不是结构本身来做到这一点。我会编辑我的答案
【解决方案2】:

我非常喜欢问题Strongly typed using and typedef 的作者的工作团队提出的解决方案,因为它真的很简单:

#define STRONG_TYPEDEF(T, D)                                 \
    class D : public T {                                     \
    public:                                                  \
        template<class... A> explicit D(A... a) : T(a...) {} \
        const T& raw() const { return *this; }               \
    }

不幸的是,它不适用于原始类型。但它类似于BOOST_STRONG_TYPEDEF,可以。

这是一个小例子和两者的残留“比较”:

#include <iostream>

#define USE_BOOST true

#if USE_BOOST

#include <boost/serialization/strong_typedef.hpp>
BOOST_STRONG_TYPEDEF(std::string, foo);
BOOST_STRONG_TYPEDEF(std::string, bar);

BOOST_STRONG_TYPEDEF(int, myInt); // Boost allows primitives

#else

#define STRONG_TYPEDEF(T, D)                                 \
    class D : public T {                                     \
    public:                                                  \
        template<class... A> explicit D(A... a) : T(a...) {} \
        const T& raw() const { return *this; }               \
    }

STRONG_TYPEDEF(std::string, foo);
STRONG_TYPEDEF(std::string, bar);

// STRONG_TYPEDEF(int, myInt); // this one just classes

#endif

int main()
{
    std::string a = "abc";
    foo b{"abc"};
    bar c = static_cast<bar>(a);
    std::string d = c; // we can assign back to base type

#if USE_BOOST // Boost
    myInt x{4};  // only allows this type of initialization
    switch (x) { // allows primitives, so `switch` works
        case 1:
            std::cout << 1 << std::endl;
            break;
        case 4:
            std::cout << 4 << std::endl;
            break;
    }
#else // Boost don't allow the following:
    if (b == c) {                    // comparing
        std::cout << c << std::endl; // printing
    }
#endif

    /* But we can't
       foo e = a; // assign base type to new type
       foo f = c; // assign one type to another
    */

    return 0;
}

【讨论】:

  • 常量在哪里?它们还能用作switch 案例标签吗?
  • @Kaz Boost 允许原语,所以在这种情况下switch 可以工作
【解决方案3】:

如果大小不必是一个字节,我们可以(ab)使用指针:

typedef struct foo *rolloverdetection;
typedef struct bar *rolloverdetected;

#define ROLLOVERDETECTION_OFF    ((rolloverdetection) 0)
#define ROLLOVERDETECTION_ON     ((rolloverdetection) 1)

#define ROLLOVERDETECTED_NO      ((rolloverdetected) 0)
#define ROLLOVERDETECTED_YES     ((rolloverdetected) 1)
#define ROLLOVERDETECTED_UNKNOWN ((rolloverdetected) 2)

预处理器符号不再是常量表达式,我们不能将它们用作开关标签等。

一个很好的解决方案是使用 C++ 类型安全的enums。这是在“干净的 C”中编写代码的优势之一:一个非正式的名称,用于以一种语言方言工作,该方言编译为某些版本的 C,以及具有相同行为的某些版本的 C++。

简单地说:

typedef enum {
  ROLLOVERDETECTION_OFF,
  ROLLOVERDETECTION_ON
} rolloverdetection;

typedef enum {
  ROLLOVERDETECTED_NO,
  ROLLOVERDETECTED_YES,
  ROLLOVERDETECTED_UNKNOWN = 255
} rolloverdetected;

在 C 中,您仍然可以将 ROLLOVERDETECTED_YES 分配给 rolloverdetection 类型的变量,但在 C++ 中则不然。

如果您将代码保持为 C++ 编译,则可以使用 C++ 编译器检查这些违规行为,即使代码的发布版本不使用 C++。

如果将值存储在 8 位中很重要,我似乎记得 GCC 支持枚举类型的位域作为扩展(不在 ISO C 中):

struct whatever {
  rolloverdetected roll_detect : 8;
};

C++ 枚举不是完全类型安全的;从enum 成员到整数类型的隐式转换是可能的:

int roll = ROLLOVERDETECTION_ON;

但不是相反的方向。

顺便说一句,如果您编译为 C 和 C++,则可以使用其他技术,例如能够使用更细微的类型转换:

#ifdef __cplusplus
#define strip_qual(TYPE, EXPR) (const_cast<TYPE>(EXPR))
#define convert(TYPE, EXPR) (static_cast<TYPE>(EXPR))
#define coerce(TYPE, EXPR) (reinterpret_cast<TYPE>(EXPR))
#else
#define strip_qual(TYPE, EXPR) ((TYPE) (EXPR))
#define convert(TYPE, EXPR) ((TYPE) (EXPR))
#define coerce(TYPE, EXPR) ((TYPE) (EXPR))
#endif

所以现在,例如,我们可以这样做

strip_qual(char *, str)

在 C 中,这只是一个不安全的 (char *) str 转换。在 C++ 中,上述宏产生const_cast&lt;char *&gt;(str)。因此,如果str 开始是const char *,但后来有人将其更改为const wchar_t *,C++ 编译器将诊断上述转换。然而,我们的项目并不需要 C++ 编译器来构建。

与此相结合,如果您使用的是 GCC,它的 C++ 前端有 -Wold-style-cast,它将找到代码中您使用 (type) value 转换符号的所有位置。

【讨论】:

    猜你喜欢
    • 2010-12-03
    • 2022-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    相关资源
    最近更新 更多