c语言 - 用"About size_t and ptrdiff_t"解释这段经文



在Andrey Karpov题为"关于size_tptrdiff_t"的博客文章中,他以结束

正如读者所看到的,使用ptrdiff_t和size_t类型为64位程序提供了一些优势。然而,它并不是用size_t替换所有未签名类型的全面解决方案首先,它不能保证程序在64位系统上正确运行。其次,由于这种替换,很可能会出现新的错误,违反数据格式兼容性等等。你不应该忘记,在这种替换之后,程序所需的内存大小也会大大增加增加必要的内存大小将减缓应用程序的工作,因为缓存将存储更少的处理对象。

我不理解这些说法,在的文章中也没有提到

"很可能由于这种替换,会出现新的错误,违反数据格式兼容性,等等。">

这怎么可能,在迁移之前怎么可能没有错误,并且类型迁移会导致错误?目前尚不清楚什么时候类型(size_tptrdiff_t)似乎比它们所取代的更具限制性。

您不应该忘记,在进行此替换后,程序所需的内存大小也将大大增加

我不清楚所需的内存大小是如何或为什么会"大幅"增加的,或者根本不会增加?不过我明白,如果真的发生了,安德烈的结论也随之而来。

这篇文章包含了非常可疑的声明。

首先,size_tsizeof返回的类型。uintptr_t是一个整数类型,可以存储指向void的任何指针。

文章认为size_tuintptr_t是同义词。他们不是。例如,在具有大内存的分段MSDOS上数组将适合16位的size_t,但指针需要32位。它们现在是我们常见的Windows、Linux平面内存模型的代名词。

更糟糕的是,声称您可以将指针存储在ptrdiff_t中,或者它将与intptr_t:同义

size_tptrdiff_t的大小始终与指针的大小一致。正因为如此,这些类型应该用作大型数组的索引、指针的存储以及指针算术。

这根本不是真的。ptrdiff_t是指针减法值的类型,但指针减法仅在两个指针都指向同一对象或在其后面,而不是内存中的任何位置时定义

另一方面,ptrdiff_t可以被选择为比size_t大,这是因为如果您有一个大小大于MAX_SIZE / 2元素的数组,那么如果ptrdiff_tsize_t的宽度相同,则从指向最后一个元素的指针中减去指向第一个元素的一个指针或刚好超过该指针将具有未定义的行为。在Inded中,标准确实规定size_t只能是16位宽,但ptrdiff_t必须至少是17](http://port70.net/~nsz/c/c11/n1570.html#7.20.3)。

在Linux上,ptrdiff_tsize_t的大小相同,并且可以在32位Linux上分配一个大于PTRDIFF_MAX元素的对象。正如评论中所指出的,标准甚至不要求ptrdiff_tsize_t具有相同的级别,尽管这样的实现是纯粹的邪恶。

如果要遵循建议并使用size_tptrdiff_t来存储指针,那么肯定不能正确。


关于

您不应该忘记,在进行此替换后,程序所需的内存大小也将大大增加。

我对这一说法提出质疑-与已经存在的由于通用64位对齐、堆栈对齐和64位指针(移动到64位环境中固有的)而增加的消耗相比,内存需求的增加将相当适度。

关于

"很可能由于这种替换,会出现新的错误,违反数据格式兼容性,等等。">

这当然是真的,但最有可能的是,如果你正在编写这样的错误代码,你会意外地"修复"过程中的旧错误,比如signed/unsigned int示例:

int A = -2;
unsigned B = 1;
int array[5] = { 1, 2, 3, 4, 5 };
int *ptr = array + 3;
ptr = ptr + (A + B); //Error
printf("%in", *ptr);

其中,原始代码和新代码都将具有未定义的行为(越界访问数组元素),但新代码在64位平台上看起来也是"正确的"。

任何更改都可能导致错误。具体来说,我可以想象,在对类型不太严格的地方,改变大小可能会打破(例如,假设int或long与指针相同,而它们不相同)。任何写入文件的二进制结构都不能直接读取,任何RPC都可能失败,具体取决于协议。

随着大多数内存中对象的大小增加,内存需求将明显增加。大多数数据将在64位边界上对齐,这意味着更多的"洞"。堆栈使用率将增加,可能导致更频繁的缓存未命中。

虽然所有的概括都可能是正确的或错误的,但唯一的方法是对手头的系统进行适当的分析。

作为一个一般命题,使用size_tptrdiff_t比使用普通的unsigned intint要好得多。CCD_ 36和CCD_。

然而:没有免费午餐这回事。正确使用size_t也需要一些工作——只是,如果你知道自己在做什么,那么与不使用size_t来实现相同的结果相比,所需的工作更少。

此外,size_t存在无法使用%d%u打印的问题。理想情况下,您希望使用%zu,但不幸的是,并非所有实现都支持它

如果你有一个不使用size_t的大型且写得很糟糕的程序,那么它可能充满了错误。其中一些bug会被掩盖或被处理掉。如果您尝试将其更改为使用size_t,那么程序的某些变通方法将失败,可能会发现曾经隐藏的错误。最终,你会解决这些问题,并实现你想要的更健壮、更可靠、更便携的程序,但这个过程将是一个艰难的过程。我怀疑这就是作者所说的"很可能由于这种替换,会出现新的错误"。

将程序更改为使用size_t有点像试图在所有正确的位置添加const。你做了你认为需要做的更改,然后重新编译,你会得到一堆错误和警告,你会修复这些错误和警告并重新编译,然后你会得到更多的错误和警告等等。这至少是一个麻烦,有时还需要大量的工作。但是,如果您想使代码更加健壮和可移植,这通常是唯一的方法。

问题的很大一部分是让编译器满意。它会对一堆东西发出警告,你通常会想修复它抱怨的一切,尽管它抱怨的一些东西很棘手,不太可能引起问题。但说"是的,我可以忽略这个特别的警告"是危险的,所以最终,正如我所说,你通常会想解决所有问题。

作者最引人注目的说法是

程序所需的内存大小也将大大增加。

我怀疑这是夸大其词——在大多数情况下,我怀疑内存会"大大"增加——但它可能至少会增加一点。问题是在64位系统上,size_tptrdiff_t可能是64位类型。无论出于什么原因,如果你有这些的大数组,或者包含这些的大结构数组,如果你以前使用过一些32位类型(可能是普通的intunsigned int),是的,你会看到内存增加。

然后你会想问,我真的需要能够描述64位大小吗64位编程提供了两个功能:(a)寻址超过4Gb内存的能力,以及(b)拥有大于4Gb的单个对象的能力。如果您希望总数据使用量大于4Gb,但不需要有一个大于4Gb的对象,并且如果您从不希望一次从文件中读取超过4Gb的数据(使用单个readfread调用),则不需要到处都有64位大小的变量。

因此,为了避免膨胀,您可能会做出明智的选择,在某些地方使用unsigned int(甚至unsigned short)而不是size_t。举个微不足道的例子,如果你有

size_t x = sizeof(int);
printf("%zun", x);

你可以把它改成

unsigned int x = sizeof(int);
printf("%un", x);

在不损失可移植性的情况下,因为我可以非常自信地保证您的代码永远不会在具有34359738368位ints的机器上运行(或者至少在我们的有生之年不会:-)。

但最后一个例子虽然微不足道,但也说明了其他容易出现的问题。类似代码

unsigned int x = sizeof(y);
printf("%un", x);

显然并不安全,因为无论y是什么,它都有可能太大,以至于它的大小不适合无符号的int。因此,如果你或你的编译器真的关心类型的正确性,那么在将size_t分配给unsigned int时,可能会出现数据丢失的警告。为了关闭这些警告,您可能需要显式强制转换,如

unsigned int x = (unsigned int)sizeof(int);

可以说,这个演员阵容非常合适。编译器的操作假设任何对象都可能非常大,任何将size_t插入unsigned int的尝试都可能丢失数据。演员们说你已经考虑过这个案例:你说,"是的,我知道,但在这个案例中,我知道它不会溢出,所以请不要再警告我这个,但请警告我其他任何可能不那么安全的案例。">

附言:我被否决了,所以如果我给人留下了错误的印象,让我明确表示(正如我在开场白中所说的)size_tptrdiff_t是非常受欢迎的。总的来说,有充分的理由使用它们,没有充分的理由不使用它们。(说到这里,Karpov也没有说不要使用它们——只是强调了一路上可能出现的一些问题。)

最新更新