在“关于size_t和ptrdiff_t”中解释此段落

时间:2018-07-07 03:32:26

标签: c pointers types stdint errata

在此blog entry by Andrey Karpov entitled, "About size_t and ptrdiff_t"中,他的结论是

  

如读者所见,使用ptrdiff_t和size_t类型为64位程序提供了一些优势。但是,这不是用size_t替换所有无符号类型的全面解决方案。 首先,它不能保证程序在64位系统上的正确运行。其次,很可能由于此替换,将出现新的错误,将破坏数据格式兼容性,依此类推。您不要忘记,替换后,程序所需的内存大小也会大大增加。必要内存大小的增加将减慢应用程序的工作速度,因为缓存将存储更少的正在处理的对象。

我不理解这些声明,也看不到文章中提到的问题,

  

“很可能由于此替换,将出现新的错误,将违反数据格式兼容性,等等。”

在迁移和类型迁移导致错误之前,怎么可能没有错误?尚不清楚何时(size_tptrdiff_t)类型比它们要替换的类型更具约束性。

  

您不要忘记替换后,程序所需的内存大小也会大大增加。

我不清楚所需的内存大小将如何或为什么“大大”增加或根本增加?不过我了解,如果这样做的话,安德烈的结论就可以了。

3 个答案:

答案 0 :(得分:3)

本文包含非常可疑的声明。

首先,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_tsize_t-这是因为如果数组的大小大于MAX_SIZE / 2个元素,如果ptrdiff_tsize_t的宽度相同,则从指向最后一个元素的指针减去指向最后一个元素的指针或仅指向其后的元素将具有未定义的行为。确实,标准确实指出size_t只能是16位宽,但是ptrdiff_t必须至少为 17 ](http://port70.net/~nsz/c/c11/n1570.html#7.20.3)。

在Linux上,ptrdiff_tsize_t的大小相同-和it is possible to allocate an object on 32-bit Linux that is larger than PTRDIFF_MAX elements。正如评论中指出的那样,尽管这样的实现纯粹是邪恶的,但标准并不需要ptrdiff_tsize_t的等级相同。

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


关于

的主张
  

您不要忘记,替换后,程序所需的内存大小也会大大增加。

我会争辩说-与一般的64位对齐,堆栈的对齐和移至64位所固有的64位指针已经引起的消耗相比,内存需求的增长将是相当适度的位环境。

关于该主张

  

“由于这种替换,很可能会出现新的错误,会破坏数据格式的兼容性,等等。”

这当然是正确的,但是很可能如果您正在编写这样的错误代码,您会偶然地“修复”过程中的旧错误,例如signed/unsigned int示例:< / p>

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

原始代码和新代码都将具有未定义的行为(超出范围访问数组元素),但是新代码在64位平台上也似乎是“正确的”。 / p>

答案 1 :(得分:1)

任何更改都可能导致错误。具体来说,我可以想象更改大小可能会在应用类型不太严格的地方中断(例如,假设int或long与不使用int的指针相同)。写入协议的任何二进制结构都无法直接读取,并且任何RPC都可能失败,具体取决于协议。

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

尽管所有概括都是对还是错,但唯一的发现方法是对手头的系统进行适当的分析。

答案 2 :(得分:0)

一般而言,使用size_tptrdiff_t比使用简单的unsigned intint更为可取。 size_tptrdiff_t几乎是编写健壮且可移植的程序的唯一方法。

但是:没有免费的午餐之类的东西。正确地使用size_t也会花费一些工作-只是,如果您知道自己在做什么,则比不使用size_t来获得相同结果所需的工作要少。

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

如果您有一个大型且编写不佳的程序,没有使用size_t,则可能存在很多错误。其中一些错误将被掩盖或解决。如果尝试将其更改为使用size_t,则该程序的某些解决方法将失败,可能会发现曾经隐藏的错误。最终,您将解决这些问题,并实现所需的更健壮,更可靠和更便携的程序,但是过程将是艰难的。我怀疑这就是作者的意思,“很可能由于这种替换,将会出现新的错误”。

更改程序以使用size_t有点像尝试在所有正确的位置添加const。您进行了您认为需要进行的更改,然后重新编译,并且收到了一堆错误和警告,然后对它们进行了修复和重新编译,并且收到了一堆 more 错误和警告等。这至少是一件令人讨厌的事情,有时还需要大量工作。但是,如果要使代码更健壮和可移植,这通常是唯一的方法。

问题的很大一部分是使编译器满意。它会警告一堆东西,您通常会想修复它抱怨的所有东西,即使它抱怨的有些棘手且不太可能引起问题。但是说“是的,我可以忽略此特定警告”是很危险的,因此最后,正如我所说,您通常会想解决所有问题。

作者最引人注目的说法是

  

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

我怀疑这是一种夸张-在大多数情况下,我怀疑内存会“大大”增加-但可能至少会增加一点。问题是在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("%zu\n", x);

您可以将其更改为

unsigned int x = sizeof(int);
printf("%u\n", x);

不会造成任何可移植性损失,因为我可以非常有把握地保证您的代码永远不会在具有34359738368位int的计算机上运行(或者至少在我们的生命周期内不行:-))

但是,最后一个例子,尽管微不足道,但它也说明了其他容易引起注意的问题。相似的代码

unsigned int x = sizeof(y);
printf("%u\n", x);

显然不是那么安全,因为无论y是什么,它都有可能太大而以至于其大小不能容纳无符号int。因此,如果您或您的编译器确实在乎类型正确性,则在将size_t分配给unsigned int时可能会警告可能丢失数据。要关闭这些警告,您可能需要进行明确的强制转换,如

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

可以说,这个演员阵容非常合适。编译器在假设任何对象可能真的很大的前提下进行操作,任何将size_t塞入unsigned int的尝试都可能会丢失数据。演员表表明您已经考虑过这种情况:您是在说:“是的,我知道,但是在这种情况下,我知道它不会溢出,所以请不要警告我再说一遍,但是请对其他任何警告我,那可能不是那么安全。”

P.S。我很沮丧,因此,如果我给人留下了错误的印象,请让我明确指出(如我在开始的段落中所述),size_tptrdiff_t非常受欢迎。通常,有充分的理由使用它们,没有充分的理由不使用它们。 (为此,Karpov也没有说不使用它们,只是强调了在此过程中可能出现的一些问题。)