【问题标题】:fixed CHAR_BIT on various systems?修复了各种系统上的 CHAR_BIT?
【发布时间】:2013-11-11 14:15:39
【问题描述】:

我对limits.h 中的CHAR_BIT 感到困惑。我读过一些文章说宏 CHAR_BIT 是为了可移植性。在代码中使用宏而不是像 8 这样的幻数,这是合理的。但是limits.h来自glibc-headers,它的值固定为8。如果glibc-headers安装在一个字节超过8位(比如16位)的系统上,编译时会出错吗? 'char' 分配 8 位或 16 位?

而当我在limits.h中将CHAR_BIT修改为9,下面的代码还是打印'8',怎么办?

#include <stdio.h>
#include <limits.h>

int
main(int argc, char **argv)
{
    printf("%d\n", CHAR_BIT);
    return 0;
}

以下是补充: 我已经阅读了所有回复,但仍然不清楚。在实践中,#include &lt;limits.h&gt; 并使用 CHAR_BIT,我可以遵守。但那是另一回事。这里我想知道为什么会这样,首先它是glibc /usr/include/limits.h中的一个固定值'8',当那些有1字节!= 8位的系统用glibc安装时会发生什么;然后我发现值'8'甚至不是代码使用的真实值,所以'8'意味着什么?如果根本不使用该值,为什么要放'8'呢?

谢谢,

【问题讨论】:

  • 也许编译器发现的limits.h 与您更改的不同。尝试注释掉 CHAR_BIT 看看是否仍然可以编译。如果是这样,您有超过 1 个 limits.h 文件。
  • @JimRhodes,你的意思是注释掉#include &lt;limits.h&gt;?编译失败。 CHAR_BIT 未声明。
  • 您确定您编辑了正确的#ifdef 分支吗?如果您查看任何标准库头文件,您将看到各种预处理器分支,并且CHAR_BIT 可能在几十个不同的地方定义。无论如何,您都不应该编辑这些文件,它们仅供参考。
  • @AndonM.Coleman,我明白了。我注释错了地方。。这次我把limits.h中的CHAR_BIT注释掉,编译成功,所以从其他头文件中获取CHAR_BIT。

标签: char bit glibc


【解决方案1】:

重点是,当为具有不同大小的系统进行编译时,CHAR_BIT 会更改为正确的大小。

【讨论】:

  • CHAR_BIT 是如何变化的?我下载并解压了glibc,解压出来的limits.h里面是8。
  • 对于那个系统。您认为 不同 系统的 glibc 可能会有所不同吗?为什么要为错误的架构使用标头?
  • 我的意思是我下载了 glibc 源代码,在 limits.h 中看到它是 8。此时它与任何系统无关,只是源文本。当我编译 glibc 时,该值是否会更改为系统上一个字节的实际位数?例如,如果 glibc 在 9 位字节系统上编译,limits.h 将具有 #define CHAR_BIT 9?
【解决方案2】:

CHAR_BIT 不应该为给定的系统更改。 CHAR_BIT 的值指定了最小可寻址存储单元(“字节”)的大小,因此即使是使用 16 位字符(UCS-2 或 UTF-16)的系统也很可能具有 @987654324 @。

几乎所有现代系统都有CHAR_BIT == 8;某些 DSPs 的 C 实现可能会将其设置为 16 或 32。

CHAR_BIT 的值不控制字节中的位数,它记录它,并允许用户代码引用它。例如,一个对象的位数是sizeof object * CHAR_BIT

如果您编辑系统的&lt;limits.h&gt; 文件,这不会改变系统的实际特性;它只是给你一个不一致的系统。这就像破解你的编译器,所以它定义符号_win32 而不是_linux;这不会神奇地将您的系统从 Windows 更改为 Linux,只会破坏它。

CHAR_BIT 是每个系统的只读常量。它由系统的开发人员定义。你不能改变它;甚至不要尝试。

据我所知,glibc 仅适用于 8 位字节的系统。理论上可以对其进行修改以使其在其他系统上运行,但如果没有大量的开发工作,您甚至可能能够将其安装在具有 16 位字节的系统上。

至于为什么破解limits.h 文件并没有改变你为CHAR_BIT 获得的值,系统标头很复杂,不打算就地编辑。当我在我的系统上编译一个只有#include &lt;limits.h&gt; 的小文件时,它直接或间接包括:

/usr/include/features.h
/usr/include/limits.h
/usr/include/linux/limits.h
/usr/include/x86_64-linux-gnu/bits/local_lim.h
/usr/include/x86_64-linux-gnu/bits/posix1_lim.h
/usr/include/x86_64-linux-gnu/bits/posix2_lim.h
/usr/include/x86_64-linux-gnu/bits/predefs.h
/usr/include/x86_64-linux-gnu/bits/wordsize.h
/usr/include/x86_64-linux-gnu/gnu/stubs-64.h
/usr/include/x86_64-linux-gnu/gnu/stubs.h
/usr/include/x86_64-linux-gnu/sys/cdefs.h
/usr/lib/gcc/x86_64-linux-gnu/4.7/include-fixed/limits.h
/usr/lib/gcc/x86_64-linux-gnu/4.7/include-fixed/syslimits.h

其中两个文件具有用于CHAR_BIT#define 指令,一个将其设置为8,另一个将其设置为__CHAR_BIT__。我不知道(我也不需要关心)这些定义中的哪些实际生效。我只需要知道#include &lt;limits.h&gt; 将为CHAR_BIT 给出正确的定义——只要我不做任何破坏系统的事情。

【讨论】:

  • @KeithThompson,感谢您的详细解释。但我还有更多问题......如何理解 CHAR_BIT 的值记录了一个字节中的位数?这是否意味着标头仅用于查看而不是使用?是不是只让我们知道那些宏名的存在?将值改为 9 不会生效,那为什么要放一个数字,比如 '8' 呢?为什么要留空?
  • @password636:“文档”可能不是最好的词。它主要用于代码;例如对象中的位数是sizeof obj * CHAR_BIT。请参阅我更新的答案的第三段。
  • @KeithThompson,它是供使用的,但从实践来看,/usr/include/limits.h 中的值 '8' 不是代码(我上面的示例)使用的,因为如果我将值改为'9'或注释掉CHAR_BIT宏定义,代码仍然可以编译成功。这意味着 /usr/include/limits.h 中的值 '8' 无关紧要,即使是那里的整个宏定义,对吧?包括 实际上是从一些头文件中引导您 real CHAR_BIT 定义。因此,如果它的真正定义不存在,为什么要在 /usr/include/limits.h 中放置 CHAR_BIT 定义?标准要求?
  • @KeithThompson,跟进,如果更改值或注释掉无关紧要,更改有什么害处?它甚至不是真正的 CHAR_BIT 定义。
  • @password636:系统标头很复杂(并且不是专门为大多数用户设计的)。我不知道为什么在一个文件中更改 CHAR_BIT 的值并没有破坏任何东西。也许您更改的文件用于不同的配置。如果您打开汽车的引擎盖并用锤子敲击某个随机部件,您的汽车可能仍会运行。
【解决方案3】:

深入研究系统头文件可能是令人生畏和不愉快的经历。 glibc 头文件很容易在您的脑海中造成很多混乱,因为它们在某些情况下包含其他系统头文件,这些头文件会覆盖迄今为止定义的内容。

limits.h为例,如果你仔细阅读头文件,你会发现CHAR_BIT的定义只在你编译没有gcc的代码时使用,因为这行:

#define CHAR_BIT 8

在上面几行的 if 条件内:

/* If we are not using GNU CC we have to define all the symbols ourself.
   Otherwise use gcc's definitions (see below).  */
#if !defined __GNUC__ || __GNUC__ < 2

因此,如果您使用 gcc 编译代码(很可能是这种情况),则不会使用 CHAR_BIT 的此定义。这就是为什么您更改它并且您的代码仍然打印旧值的原因。在头文件上向下滚动一点,您可以在使用 GCC 的情况下找到它:

 /* Get the compiler's limits.h, which defines almost all the ISO constants.

    We put this #include_next outside the double inclusion check because
    it should be possible to include this file more than once and still get
    the definitions from gcc's header.  */
#if defined __GNUC__ && !defined _GCC_LIMITS_H_
/* `_GCC_LIMITS_H_' is what GCC's file defines.  */
# include_next <limits.h>

include_next 是 GCC 扩展。您可以在这个问题中了解它的作用:Why would one use #include_next in a project?

简短的回答:它将搜索具有您指定名称的下一个头文件(在这种情况下为limits.h),并且它将包括GCC生成的limits.h。在我的系统中,它恰好是/usr/lib/gcc/i486-linux-gnu/4.7/include-fixed/limits.h

考虑以下程序:

#include <stdio.h>
#include <limits.h>

int main(void) {
  printf("%d\n", CHAR_BIT);
  return 0;
}

使用这个程序,您可以在gcc -E 的帮助下找到系统的路径,它会为包含的每个文件输出一个特殊行(请参阅http://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html

因为#include &lt;limits.h&gt;在这个程序的第2行,我将它命名为test.c,运行gcc -E test.c可以让我找到包含的真实文件:

# 2 "test.c" 2
# 1 "/usr/lib/gcc/i486-linux-gnu/4.7/include-fixed/limits.h" 1 3 4

你可以在那个文件中找到这个:

/* Number of bits in a `char'.  */
#undef CHAR_BIT
#define CHAR_BIT __CHAR_BIT__

注意undef 指令:它需要覆盖任何可能的先前定义。它是在说:“忘记CHAR_BIT 是什么,这是真的”。 __CHAR_BIT__ 是一个 gcc 预定义常量。 GCC 的在线文档是这样描述的:

__CHAR_BIT__ 定义为用于表示 char 数据类型的位数。它的存在是为了使标准标题给定数字 限制正常工作。你不应该直接使用这个宏; 而是包含适当的标题。

你可以用一个简单的程序来读取它的值:

#include <stdio.h>
#include <limits.h>

int main(void) {
  printf("%d\n", __CHAR_BIT__);
  return 0;
}

然后运行gcc -E code.c。请注意,您不应该直接使用它,正如 gcc 的手册页所述。

显然,如果您在/usr/lib/gcc/i486-linux-gnu/4.7/include-fixed/limits.h 中更改CHAR_BIT 定义,或系统中的任何等效路径,您将能够在代码中看到此更改。考虑这个简单的程序:

#include <stdio.h>
#include <limits.h>

int main(void) {
  printf("%d\n", CHAR_BIT);
  return 0;
}

将 gcc 的limits.h(即/usr/lib/gcc/i486-linux-gnu/4.7/include-fixed/limits.h 中的文件)中的CHAR_BIT 定义从__CHAR_BIT__ 更改为9 将使此代码打印9。同样,您可以在预处理发生后停止编译过程;您可以使用gcc -E 对其进行测试。

如果您使用 gcc 以外的编译器编译代码怎么办?

好吧,就这样吧,默认 ANSI 限制假定用于标准 32 位字。来自 ANSI C 标准中的第 5.2.4.2.1 段(整数类型的大小&lt;limits.h&gt;):

下面给出的值应替换为常量表达式 适用于#if 预处理指令。 [...] 他们的 实现定义的值应在大小上相等或更大 (绝对值)与所示的相同,符号相同。

  • 不是位域(字节)的最小对象的位数

    CHAR_BIT 8

POSIX 要求合规平台具有CHAR_BIT == 8

当然,对于没有CHAR_BIT == 8 的机器,glibc 的假设可能会出错,但请注意,您必须在不寻常的架构下并且不使用 gcc 并且您的平台不兼容 POSIX。不太可能。

但是请记住,“实现定义”意味着编译器编写者选择发生的事情。因此,即使您没有使用gcc 进行编译,您的编译器也有可能定义了某种__CHAR_BIT__ 等效项。即使 glibc 不会使用它,您也可以做一些研究并直接使用编译器的定义。这通常是不好的做法 - 您将编写面向特定编译器的代码。

请记住,您永远不应该弄乱系统头文件。当您使用错误且重要的常量(如 CHAR_BIT)编译内容时,可能会发生非常奇怪的事情。仅出于教育目的执行此操作,并始终恢复原始文件。

【讨论】:

  • 非常详细的技术讲解!很好的解释!
  • 更正一个:“GCC的在线文档是这样描述的”应该是__CHAR_BIT__
  • @password636 谢谢,已修复。
  • 非常详细的解释。 gcc -E 方法将我引导到文件 /usr/include/limits.h,它实际上包含所有定义的常量,因为这是最后一个包含的常量,所以您的环境也是如此设置的。值得注意的是,它查看__WORDSIZE 以确定某些值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-01-31
  • 1970-01-01
  • 2020-04-30
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
  • 2018-11-01
相关资源
最近更新 更多