当前位置: 首页 > 知识库问答 >
问题:

我什么时候应该只使用“int”而不是更多特定于符号或特定于大小的类型?

丁书
2023-03-14

我有一个用C实现的编程语言的小VM。它支持在32位和64位架构以及C和C下编译。

我正试图让它以尽可能多的警告来干净地编译。当我打开CLANG_WARN_IMPLICIT_SIGN_CONVERSION时,我会收到一连串新的警告。

对于何时使用int而不是显式无符号类型和/或显式大小的类型,我想有一个很好的策略。到目前为止,我很难决定应该采取什么策略。

当然,混合它们——主要使用int来处理局部变量和参数之类的事情,并为结构中的字段使用较窄的类型——会导致许多隐式转换问题。

我确实喜欢为结构字段使用更具体大小的类型,因为我喜欢显式控制堆中对象的内存使用的想法。此外,对于哈希表,我在进行哈希运算时依赖于无符号溢出,因此如果哈希表的大小存储为uint32\t,那就更好了。

但是,如果我尝试在任何地方使用更具体的类型,我会发现自己陷入了到处都是扭曲的迷宫。

其他C项目做什么?

共有3个答案

锺离烈
2023-03-14

将用于访问数组成员或控制缓冲区的大量数字保留为size\u t

有关使用size\u t的项目示例,请参阅GNU的dd.c,第155行。

汪正卿
2023-03-14

从我浏览的链接源代码来看,您似乎知道自己在做什么。

你自己说的——使用“特定”类型会让你拥有更多的演员阵容。无论如何,这不是一条最佳路线。尽可能多地使用int,用于不需要更专业类型的东西。

int的美妙之处在于它对您所说的类型进行了抽象。在您不需要将构造暴露给不知道int的系统的所有情况下,它都是最佳的。它是您自己的工具,用于为您的程序抽象平台。它还可能为您带来速度、大小和对齐优势,具体取决于。

在所有其他情况下,例如,如果您希望故意保持接近机器规格,int可以,有时应该放弃。典型的例子包括数据传输的网络协议,以及互操作性设施——C语言和其他语言之间的某种桥梁,访问C结构的内核汇编例程。但别忘了,即使在这些情况下,有时你也会想实际使用int,因为它遵循的是平台自己的“本机”或首选字号,而你可能想要依赖这个属性。

对于像uint32_t这样的平台类型,如果从C和汇编器都访问这些,内核可能希望在其数据结构中使用这些(尽管它可能不必),因为后者通常不知道int应该是什么。

总而言之,尽可能使用int,并在任何可能需要的情况下从更抽象的类型转移到“机器”类型(字节/八位字节、单词等)。

至于size_t和其他“用法提示”类型——只要语法遵循类型固有的语义——比如,使用size_t以及各种大小值——我不会反对。但我不会因为它被保证是最大的类型(不管它是否真的是真的)而随意地将其应用于任何事情。那是一块水下石头,你以后不想踩上去。我想说,代码必须尽可能地自我解释——如果有一个大小_t,而在自然情况下不需要,这会让人大吃一惊,这是有充分理由的。使用size\t查看尺码。使用offset\t进行偏移。用[u]intN\t表示八位字节、单词等。等等

这是关于将特定C类型中固有的语义学应用到您的源代码中,以及对正在运行的程序的影响。

此外,正如其他人所说明的,不要回避typedef,因为它使您能够有效地定义自己的类型,这是我个人重视的抽象工具。一个好的程序源代码甚至可能不会公开一个int,但依赖于int在许多目的定义的类型后面别名。我不打算在这里介绍typedef,希望其他答案会。

呼延源
2023-03-14

到处使用int可能看起来很诱人,因为它最大限度地减少了对转换的需求,但您应该注意几个潜在的陷阱:

>

  • int可能比您预期的要短。尽管在大多数桌面平台上,int通常为32位,但C标准仅保证最小长度为16位。你的代码需要大于2的数字吗−1=32767,即使是临时值?如果是这样,不要使用int。(您可能希望改用long;保证long至少为32位。)

    即使是long也可能并不总是足够长。特别是,无法保证数组(或字符串的长度,它是char数组)的长度适合long。为那些使用size_t(或ptrdiff_t,如果您需要签名差异)。

    具体来说,size\u t被定义为足够大,可以容纳任何有效的数组索引,而int甚至long可能不足够。因此,例如,当在数组上迭代时,循环计数器(及其初始/最终值)通常应该是大小_t,至少除非您确定数组足够短,可以使用较小的类型。(但是向后迭代时要小心:size\t是无符号的,所以for(size\t i=n-1;i)

    签名为int。特别是,这意味着int溢出是未定义的行为。如果您的值可能存在合法溢出的风险,请不要使用int;改用无符号int(或无符号long,或uintNN\u t)。

    有时,您只需要一个固定的位长度。如果您与ABI接口,或读取/写入文件格式,需要特定长度的整数,那么这就是您需要使用的长度。(当然,在这种情况下,您可能还需要担心诸如endianness之类的事情,因此有时可能不得不求助于手动逐字节打包数据。)

    尽管如此,还是有理由避免一直使用固定长度的类型:不仅总是int32_t难以键入,而且强迫编译器始终使用32位整数并不总是最佳的,尤其是在本机int大小可能是(比如)64位的平台上。比如说,你可以使用C99int_fast32_t,但这更难打字。

    因此,以下是我个人关于最大安全性和便携性的建议:

    >

    #include <limits.h>
    typedef int i16;
    typedef unsigned int u16;
    #if UINT_MAX >= 4294967295U
      typedef int i32;
      typedef unsigned int u32;
    #else
      typedef long i32;
      typedef unsigned long i32;
    #endif
    

    如果类型的确切大小无关紧要,只要它们足够大,就可以使用这些类型。我建议的类型名称既简短又自文档化,因此它们应该易于在需要的强制转换中使用,并最大限度地减少由于使用过于狭窄的类型而导致的错误风险。

    方便的是,上述定义的u32u16类型保证至少与无符号int一样宽,因此可以安全使用,而不必担心它们被提升为int并导致未定义的溢出行为。

    对所有数组大小和索引使用size_t,但在它和任何其他整数类型之间转换时要小心。或者,如果您不喜欢键入这么多下划线,typedef也是一个更方便的别名。

    对于假设特定位数溢出的计算,要么使用uintNN_t,要么只使用u16/u32如上所定义,并使用显式位掩码

    #define u(x) (0U + (x))
    

    这样你就可以安全地书写,例如:

    uint32_t a = foo(), b = bar();
    uint32_t c = u(a) * u(b);  /* this is always unsigned multiply */
    

    对于需要特定整数长度的外部ABI,请再次定义特定类型,例如:

    typedef int32_t fooint32;  /* foo ABI needs 32-bit ints */
    

    同样,这个类型名称是自我记录的,就其大小和用途而言。

    如果ABI实际上可能需要16位或64位int,则取决于平台和/或编译时选项,您可以更改类型定义以匹配(并将类型重命名为foint)-但是每当您将任何内容转换为或转换为该类型时,您确实需要小心,因为它可能会意外溢出。

    如果您的代码有自己的结构或文件格式,需要特定的比特长度,请考虑也为这些定义自定义类型,就像它是外部ABI一样。或者您可以只使用uintNN_t代替,但这样您会丢失一点自留档。

    对于所有这些类型,不要忘记定义相应的\u MIN\u MAX常量,以便于边界检查。这听起来可能需要做很多工作,但实际上只是一个头文件中的几行。

    最后,记住要小心整数运算,尤其是溢出。例如,请记住,两个n位有符号整数的差可能不适合n位整数(如果您知道它是非负的,它将适合n位无符号整数;但请记住,在取其差之前,需要将输入转换为无符号类型,以避免未定义的行为!)类似地,要找到两个整数的平均值(例如,对于二进制搜索),不要使用avg=(lo-hi)/2,而是使用avg=lo(hi-0U-lo)/2;如果总数过多,前者将崩溃。

  •  类似资料:
    • 问题内容: 我一直在做一个Android教程,遇到了一个包含以下内容的类: 是一种按类型继承的形式吗?还是我应该了解的其他Java语法? 该类是: 问题答案: 这称为 泛型 。内的类和是一个 类型参数 。 用一个例子最容易解释: 一个可以存储项目。如果您这样指定类型参数:那么此数组列表将仅存储类型的项(换句话说,将仅存储s)! 同样,也通过类型“参数化”。将可能包含一个值,该值将与指定类型的和,而

    • 问题内容: 我是一名C ++程序员,偶尔使用MySQL处理数据库,但是我的SQL知识非常有限。但是,我当然愿意改变这一点。 目前,我正尝试仅通过SQL查询对数据库中的数据进行分析(!)。但是我将放弃,而是将数据导入C 并使用C 代码进行分析。 我已经与同事讨论了这一点,他们也促使我使用C ++,他说SQL并不是用于复杂的分析,而是主要用于导入(从现有表中)和导出(到新表中)数据,还有更多内容。例如

    • 问题内容: 我注意到,如果我对打开的文件进行迭代,则无需“读取”该文件即可更快地对其进行迭代。 即 比 第二个循环将花费大约1.5倍的时间(我在完全相同的文件上使用了timeit,结果是0.442对0.660),并且会得到相同的结果。 所以-我什么时候应该使用.read()或.readlines()? 由于我一直需要遍历正在读取的文件,并且在学习了艰难的方式之后,.read()在大数据上的运行速度

    • 问题内容: 这似乎很明显,但是我发现自己对于何时使用花括号在ES6中导入单个模块感到有些困惑。例如,在我正在从事的React- Native项目中,我具有以下文件及其内容: initialState.js 在TodoReducer.js中,我必须不带花括号将其导入: 如果将花括号括起来,则以下代码行将出现以下错误: 无法读取未定义的属性待办事项 TodoReducer.js: 带有花括号的组件也发

    • 问题内容: 有什么区别?什么时候应该使用容量为1的对抗? 问题答案: SynchronousQueue更像是一个传递,而LinkedBlockingQueue仅允许单个元素。区别在于对SynchronousQueue的put()调用直到有相应的take()调用 才返回 ,但LinkedBlockingQueue的大小为1,则put()调用(对空队列)将立即返回。 我不能说自己曾经直接使用过Sync

    • 问题内容: 我对使用和翻译有疑问。我了解到,在模型中,我应该使用。但是还有其他地方我也应该使用吗?表单定义呢?它们之间是否存在性能差异? 编辑: 还有一件事。有时候,代替被使用。正如文档所述,仅在将字符串显示给用户之前,才将字符串标记为要翻译,并在可能的最新情况下进行翻译,但是我在这里有点困惑,这与功能相似吗?我仍然很难决定在模型和表格中应该使用哪个。 问题答案: ugettext() 与 uge