【问题标题】:How to get SIZE_MAX in C89如何在 C89 中获取 SIZE_MAX
【发布时间】:2017-06-07 01:11:39
【问题描述】:

我正在尝试在 C89 中获取 SIZE_MAX

我想到了以下方法找到SIZE_MAX

const size_t SIZE_MAX = -1;

由于标准(§6.2.1.2 ANSI C)说:

当有符号整数转换为大小相等或更大的无符号整数时,如果有符号整数的值为非负数,则其值不变。否则:如果无符号整数的大小更大,则先将有符号整数提升为无符号整数对应的有符号整数; 该值被转换为无符号,方法是向它添加比无符号整数类型中可以表示的最大数字大 1 的值 28

脚注 28:

在二进制补码表示中,除了用符号位的副本填充高位之外,如果无符号整数具有更大的大小,则位模式没有实际变化。

这似乎已经定义了行为,但我不太确定我是否正确理解了该段落的措辞。

请注意,此问题明确与 C89 有关,因此 this 没有回答我的问题,因为该标准有不同的措辞。

如果这不起作用,我想出的另一种方法是:

size_t get_size_max() {
    static size_t max = 0;
    if (max == 0) {
        max -= 1U;
    }

    return max;
}

但是我在标准中找不到任何关于无符号整数下溢的信息,所以我在这里摸索着。

【问题讨论】:

  • 标准中有很多关于无符号整数溢出的内容。本质上它使用模运算。
  • @Peter:我没找到。在我拥有的副本中,下溢这个词被使用了 4 次,并且没有一个是关于无符号类型的。也从未提及模算术,只是“模”这个词。正常模不一定是为负值定义的。
  • @FSMaxB - 你没有仔细阅读,标准也没有像你期望的那样用勺子喂食。 1999 C 标准,第 6.2.5 节,第 9 段的第二句。“涉及无符号操作数的计算永远不会溢出,因为无法由生成的无符号整数类型表示的结果会以比结果类型可以表示的最大值。”。这描述了模算术的数学概念。我现在手头没有 1989 年的标准,但它肯定有具有相同净含义的词。
  • SIZE_MAX 不一定与~(size_t)0(size_t)-1 相同,因此从一开始就可以“确定”它的假设是有缺陷的。 SIZE_MAX 是由编译器设置的。 See this.

标签: c c89 size-t


【解决方案1】:

你可以使用:

#ifndef SIZE_MAX
#define SIZE_MAX ((size_t)(-1))
#endif

-1 转换为无符号整数类型的行为在 C11 6.3.1.3 “转换 - 有符号和无符号整数”部分中定义。 C89 有一个等效的定义,编号为 3.2.1.2。事实上,您在问题中引用了 ISO C90 定义 6.2.1.2(ANSI C89 和 ISO C90 之间的区别在于这些部分的编号不同)。

我不建议使用const 变量,因为它们不能用于常量表达式。


注意:这不能用于 C90 预处理算术,它只适用于不包含强制转换或单词的整数常量表达式,因此我们不能使用任何 sizeof 技巧。在这种情况下,您可能需要特定于系统的定义;预处理器没有检测 typedef 的标准方法。

【讨论】:

  • 你不能在预处理算术中转换为size_t,因为size_t是一个typedef。
  • @MM 谢谢。我不太关心预处理器算术。不过,对于可能想在预处理器步骤中使用它的其他人来说,知道如何做到这一点会很好。
  • 如果你碰巧需要它作为一个值,编译并运行#include <stdlib.h> #include <stdio.h> int main(void) { printf("#define SIZE_MAX %zu\n", (size_t)(-1)); return EXIT_SUCCESS; } 并将输出重定向到一个专用的头文件,比如size_max.h,它包含在其他源文件中。如果你使用 Makefile,它实际上很容易设置。
  • @NominalAnimal 我认为这值得作为附加答案发布。虽然它不适用于交叉编译,但如果二进制文件无法在构建系统上执行。
  • @NominalAnimal 可能添加了printf("#define SIZE_MAX %zuu\n", (size_t)(-1));u,以确保尽可能将值视为某种无符号类型。
【解决方案2】:

我建议使用M.M 的答案中描述的宏定义。

在某些情况下,您可能需要一个类似的宏,但作为一个数字常量,以便您可以在 #if VALUE > 42 ... #endif 等预处理器指令中使用它。我评论说,在这种情况下,可以在编译时运行帮助程序,以计算和打印定义此类常量的头文件。

显然,当交叉编译到不同的架构时,这将不起作用;在这种情况下,必须通过其他方式提供头文件。 (例如,项目可以有一个预生成头文件的子目录,以及每个已知架构的列表,以便用户可以简单地将头文件复制到位。)

创建一个 Makefile 和相关的工具来运行这些程序(并且只有在用户没有将头文件复制到适当位置时),并不困难。

首先,假设您的程序包含两个源文件,foo.c

#include <stdlib.h>
extern void hello(void);

int main(void)
{
    hello();
    return EXIT_SUCCESS;
}

还有一个bar.c

#include <stdio.h>
#include "size_max.h"

#define  STRINGIFY_(s) #s
#define  STRINGIFY(s) STRINGIFY_(s)

void hello(void)
{
    fputs("SIZE_MAX = \"" STRINGIFY(SIZE_MAX) "\".\n", stdout);
}

上面的 bar.c 将 SIZE_MAX 预处理器宏转换为字符串,并打印出来。如果我们有#define SIZE_MAX (size_t)(-1),它将打印SIZE_MAX = "(size_t)(-1)"

请注意,bar.c 包含文件 size_max.h,而我们没有。这是我们打算使用帮助程序 size_max.c 生成的头文件:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    printf("#ifndef SIZE_MAX\n");
    printf("#define SIZE_MAX %lluU\n", (unsigned long long)(size_t)(-1));
    printf("#endif\n");
    return EXIT_SUCCESS;
}

chux 在评论中指出u 后缀(对于足够大的无符号整数类型)可能是必要的。如果这不是您所需要的,我相信您可以修改宏生成器助手以满足您的需要。

M.M 在评论中指出 %z 不受 ANSI C/ISO C90 支持,因此上述程序首先使用 (size_t)(-1) 创建常量,然后将其转换为 unsigned long long 格式并打印出来。

现在,可以以与操作系统无关的方式编写 Makefile,但我懒得在这里这样做,所以我将使用适用于 GNU 工具的值。要使其在其他系统上也能正常工作,只需要修改

  • CC,反映你使用的编译器

  • CFLAGS,以反映您的首选编译器选项

  • LD,用于反映您的链接器,除非与 CC 相同

  • LDFLAGS,如果您需要一些链接器标志(可能是-lm?)

  • RM,反映删除不必要文件的命令

  • 文件名,如果您的构建系统需要一些时髦的可执行文件扩展名

无论如何,这是 Makefile

CC      := gcc
CFLAGS  := -Wall -O2
LD      := $(CC)
LDFLAGS := $(CFLAGS)
RM      := rm -f

# Programs to be built
PROGS   := example

# Relative path to use for executing the header generator helper program
HEADERGEN := ./headergen

# Rules that do not correspond to actual files
.PHONY: all clean headergen

# Default rule is to build all binaries
all: $(PROGS)

# Clean rule removes build files and binaries
clean:
    -$(RM) $(PROGS) $(HELPROG) *.o size_max.h

# Rule to "rebuild" size_max.h
size_max.h: size_max.c
    -@$(RM) $(HEADERGEN) size_max.h
    @$(CC) $(CFLAGS) $^ -o $(HEADERGEN)
    $(HEADERGEN) > size_max.h
    @$(RM) $(HEADERGEN)

# Rule to build object files from .c source files
%.o: %.c size_max.h
    $(CC) $(CFLAGS) -c $<

# Example binary requires foo.o and bar.o:
example: foo.o bar.o size_max.h
    $(LD) $(LDFLAGS) foo.o bar.o -o $@

注意缩进应该使用制表符,而不是空格,所以如果你复制粘贴上面的内容,运行例如sed -e 's|^ *|\t|' -i Makefile 修复它。

在压缩或压缩源代码树之前,运行 make clean 以从中删除任何生成的文件。

注意配方先决条件中额外的size_max.h。它告诉make 在完成配方之前确保size_max.h 存在。

这种方法的缺点是您不能在链接配方中使用$^ 来引用所有必备文件名。 $&lt; 指第一个必备文件名。不过,如果您使用 GNU make 或兼容的 make,则可以使用 $(filter-out %.h, %^)(列出除头文件之外的所有先决条件)。

如果您的所有二进制文件都是从同名的单一来源构建的,您可以将最后两个配方替换为

# All programs are built from same name source files:
$(PROGS): %: %.c size_max.h
    $(CC) $(CFLAGS) $< $(LDFLAGS) -o $@

在我的系统上,正在运行

make clean all && ./example

输出

rm -f example  *.o size_max.h
./headergen > size_max.h
gcc -Wall -O2 -c foo.c
gcc -Wall -O2 -c bar.c
gcc -Wall -O2 foo.o bar.o -o example
SIZE_MAX = "18446744073709551615U".

正在运行

make CC="gcc-5" CFLAGS="-Wall -std=c99 -pedantic -m32" clean all && ./example

输出

rm -f example  *.o size_max.h
./headergen > size_max.h
gcc-5 -Wall -std=c99 -pedantic -m32 -c foo.c
gcc-5 -Wall -std=c99 -pedantic -m32 -c bar.c
gcc-5 -Wall -std=c99 -pedantic -m32 foo.o bar.o -o example
SIZE_MAX = "4294967295U".

请注意,如果您更改编译器选项,如果您编辑 Makefile 或在运行 make 时使用不同的 CFLAGS=CC= 选项,make 不会检测到,因此您确实需要先指定 clean 目标,以确保新设置生效后,您可以从头开始。

在正常编辑和构建期间,当您不更改编译器或编译器选项时,无需在构建之间使用make clean

【讨论】:

  • 如果你打算假设unsigned long(如后缀所示),你也可以只做#define SIZE_MAX ULONG_MAX。在 C89 平台上,size_t 不是 unsigned_long(例如,其中一种 MS-DOS 内存模型具有 16 位 size_t 和 32 位长)。
  • 我看不出使用ULU 相比有什么好处,因为即使没有LLL,常量也会根据需要变得尽可能宽。 UL 可以使 SIZE_MAX 比需要的更宽,即使它只是一个独角兽平台。
  • @M.M:不,不是我的意图。我不知道例如C11 6.4.4.1p5,“整数常量的类型是对应列表中可以表示其值的第一个”,对应的无后缀十进制整数列表只包含@987654369 @、long intlong long int;和u-后缀包含unsigned intunsigned long intunsigned long long int的十进制整数;即,无后缀十进制常量将被签名(但根据需要大),u 后缀十进制常量将无符号但根据需要大,就像上面评论的 chux。
猜你喜欢
  • 2021-08-19
  • 1970-01-01
  • 1970-01-01
  • 2021-01-02
  • 2020-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-04
相关资源
最近更新 更多