各种系统上的CHAR_BIT是否固定?

3
我对limits.h中的CHAR_BIT感到困惑。我阅读了一些文章,称宏CHAR_BIT存在是为了可移植性。在代码中使用宏而不是像8这样的魔数是合理的。但是limits.h来自glibc-headers,其值固定为8。如果在具有多于8位(例如16位)的字节的系统上安装了glibc-headers,则编译时是否会出错?'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 <limits.h>和使用CHAR_BIT,我可以遵守这个规则。但那是另一回事。我想知道为什么它看起来是这样的,首先,在glibc /usr/include/limits.h中,它是一个固定值“8”,当安装有1字节!= 8位的系统时会发生什么;然后我发现值“8”甚至不是代码正在使用的真实值,所以“8”在那里毫无意义?如果根本不使用该值,为什么放置“8”?
谢谢,

也许编译器找到的是你修改过的不同的limits.h文件。尝试注释掉CHAR_BIT,看看是否仍然可以编译。如果可以,那么你有多个limits.h文件。 - Jim Rhodes
@JimRhodes,你的意思是将#include <limits.h>注释掉吗?编译失败了。CHAR_BIT未声明。 - password636
你确定你编辑了正确的 #ifdef 分支吗?如果你查看任何标准库头文件,你会看到各种预处理器分支,而 CHAR_BIT 可能在数十个不同的位置被定义。无论如何,你都不应该编辑这些文件,它们只是作为参考。 - Andon M. Coleman
@AndonM.Coleman,我明白了。我评论错地方了...这次我在limits.h中注释掉了CHAR_BIT,编译成功,所以它从其他头文件中获取了CHAR_BIT。 - password636
3个回答

14
深入研究系统头文件可能是一种令人望而生畏和不愉快的经历。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的一个扩展。你可以在这个问题中了解它的作用:为什么会在项目中使用#include_next?

简而言之:它会查找下一个具有指定名称(在本例中为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 <limits.h>,并且程序名为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文件中的CHAR_BIT定义(即位于/usr/lib/gcc/i486-linux-gnu/4.7/include-fixed/limits.h的文件)从__CHAR_BIT__更改为9将使此代码打印9。可以在预处理完成后停止编译过程;您可以使用gcc -E进行测试。如果您正在使用除gcc以外的编译器编译代码呢?那么就默认使用标准32位字的默认ANSI限制。从ANSI C标准(整数类型大小)的第5.2.4.2.1段开始:

下面给出的值应替换为适用于#if预处理指令的常量表达式。[...] 它们的实现定义值应等于或大于具有相同符号的那些值的数量(绝对值)。

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

    CHAR_BIT 8

POSIX规定符合规范的平台具有CHAR_BIT == 8。

当然,glibc的假设对于没有CHAR_BIT == 8的机器可能会出错,但请注意,您必须处于异常架构下,并且不使用gcc,并且您的平台不符合POSIX规范。这种情况不太可能发生。

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

请记住,永远不应该操纵系统标头文件。当您使用错误的重要常量(例如CHAR_BIT)编译东西时,非常奇怪的事情会发生。仅出于教育目的而这样做,并始终将原始文件恢复回去。


1
非常详细和技术性的解释!非常好的讲解! - password636
一个更正: "GCC 的在线文档如下描述" 应该是针对 __CHAR_BIT__ - password636
@password636 谢谢,已修复。 - Filipe Gonçalves
非常详细的解释。gcc -E 方法让我找到了文件 /usr/include/limits.h,该文件实际上包含了所有定义的常量,因为它是最后被包含的,所以您的环境也是如此设置的。有趣的是,它查看 __WORDSIZE 来确定某些值。 - Mr. Doomsbuster

6

CHAR_BIT对于给定的系统不应更改。 CHAR_BIT的值指定存储的最小可寻址单元(“字节”)的位数大小,因此即使使用16位字符(UCS-2或UTF-16)的系统通常也会具有CHAR_BIT == 8

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

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

如果编辑系统的<limits.h>文件,则不会更改系统的实际特性;它只会给你一个不一致的系统。就像黑客通过定义符号_win32而不是_linux来破解编译器一样;这并不能神奇地将您的系统从Windows变成Linux,而只会破坏它。

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

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

至于为什么黑客limits.h文件并没有更改CHAR_BIT的值,因为系统头文件很复杂,不应进行就地编辑。当我在我的系统上编译一个只有#include <limits.h>的小文件时,它直接或间接包含以下内容:

/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

其中有两个文件使用了#define指令来定义CHAR_BIT,一个将其设置为8,另一个则设置为__CHAR_BIT__。我不知道哪个定义实际上会生效(也不需要关心)。我只需要知道的是#include <limits.h>会提供正确的CHAR_BIT定义——只要我不做任何破坏系统的事情。


@KeithThompson,感谢您提供详细的解释。但我还有更多问题...如何理解CHAR_BIT的值记录了字节中的位数?这是否意味着头文件仅用于查看而非使用?它只是让我们知道这些宏名称存在吗?将该值更改为9将不会生效,那么为什么要在那里放置数字“8”?为什么不留空? - password636
@password636: “documents”可能不是最好的词。它主要用于代码中;例如,对象中的位数为sizeof obj * CHAR_BIT。请参见我更新的答案的第三段。 - Keith Thompson
@KeithThompson,跟进一下,如果更改值或注释掉不重要,那么更改会有什么危害呢?这甚至不是真正的CHAR_BIT定义。 - password636
@KeithThompson,我可以在代码中遵守规则,没有问题,但这是另一回事。在这里,我想知道为什么它看起来是那样的,它对我来说似乎很奇怪,首先在glibc /usr/include/limits.h中是一个固定值'8',当然我想知道那些具有1字节!= 8位的系统怎么办; 然后我发现值“8”甚至不是我们的代码实际使用的真实值,“8”在那里什么意思?或者其他什么? - password636
1
@password636:系统头文件很复杂(并且并不是为大多数用户设计的易读性)。我不知道为什么在一个文件中更改CHAR_BIT的值没有破坏任何东西。也许您更改的文件用于不同的配置。如果您打开汽车引擎盖并用锤子随意敲击某个部件,您的汽车可能仍然可以运行。 - Keith Thompson
显示剩余4条评论

0
整个意思是,当编译针对不同大小的系统时,CHAR_BIT会被更改为正确的大小。

CHAR_BIT发生了什么变化?我下载并提取了glibc,limits.h中提取的值为8。 - password636
针对那个系统,你认为不同系统的glibc会有所不同吗?为什么要使用错误架构的头文件? - mjs
我的意思是我下载了glibc源代码并在limits.h中看到它是8。此时它与任何系统都无关,只是源文本。当我编译glibc时,这个值会被改变为字节在系统上的实际位数吗?例如,如果在一个9位字节系统上编译glibc,limits.h将有#define CHAR_BIT 9吗? - password636

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接