是否可能初始化或终止malloc缓冲区?

3
假设我按照以下方式做某事:
size_t length = 1000;
char* p = malloc(length);

然后我想循环遍历这些元素,最基本的方法是:
for (size_t i = 0; i < length; ++i) {
  p[i] = ...; // or p[length - 1 - i] = ...
}

但也有可能是
char* q = p;
for (size_t i = 0; i < length; ++i) {
  *q = ...;
  ++q;
}

或者反过来
char* q = p + (length - 1);
for (size_t i = 0; i < length; ++i) {
  *q = ...;
  --q;
}

我的问题是,如果我想避免使用i并做如下操作:
char* const final = p + (length - 1);
for (char* q = p; q <= final; ++q) {
  *q = ...;
}

或者反过来说:
char* const final = p + (length - 1);
for (char* q = final; q >= p; --q) {
  *q = ...;
}

似乎在避免使用i的循环中存在非常微小的错误行为的可能性;对于第一个循环,如果p + length == 0,即我们在可能的size_t限制的最后一点刚好分配了内存并发生了溢出...对于第二个循环,如果p == 0,即我们在内存的开头刚好分配了内存...在这两种情况下,循环将无法在需要时结束...
也许这些情况并不真实发生,但如果这是未定义行为,那么也许使用i进行循环会更好,尽管看起来稍微不太优雅...
编辑:根据Fe2O3的评论,我想起我确实想以稍微不同的方式提问。换句话说,我想要的不是一个char数组,而是一个某种结构类型的元素数组,所以这个结构可能相对较大,比如大小为3000。那么,只要p小于3000,第二个循环就会失败,不一定要等于0。而且,只要final的大小达到最大值减去3000就足够了...当然,3000可以更大...

1
根据标准规定,“p + length”始终是正确的。对于第二种情况,请使用“do ... while(q-- > p);”。 - undefined
1
@pmg 谢谢!第二个建议实际上非常好,我之前不知为何没有考虑到。关于标准,很好知道,看起来它也解决了第一个“问题”... - undefined
1
p + length作为指针(例如用于指针比较)始终是正确的... 但是如果您对其进行解引用,则是不正确的:*(p + length)是无效的! - undefined
1
@Fe2O3 观点独到,谢谢。 - undefined
1
@Sasha - 考虑你编写软件的方式是很好的,但不要过度。 :-) 例如,如果你在任何0不等于NULL的机器上没有实际测试过你的代码,那么它很可能会在其他方面失败,比如short只有23位。可移植的代码并不会在每个地方都完全可移植。 - undefined
显示剩余11条评论
2个回答

1
TL;DR:递增指针版本是可以的,但递减指针版本是未定义的。
C标准定义了在数组中的指针算术运算只要结果指针指向数组的一个元素或者指向“超过末尾”的位置都是有效的。在这种特殊情况下,你会得到一个有效的指针,但不能对其进行解引用(这样做是未定义的),但它将始终比数组中任何元素的指针都大。
6.5.6.8 当将具有整数类型的表达式加到或从指针中减去时,结果的类型与指针操作数的类型相同。如果指针操作数指向数组对象的一个元素,并且数组足够大,那么结果将指向原始元素的偏移元素,使得结果和原始数组元素的下标之差等于整数表达式。换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+N(等价于N+(P))和(P)-N(其中N的值为n)分别指向数组对象的第i+n个和第i-n个元素,前提是它们存在。此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向数组对象的最后一个元素之后的一个元素,如果表达式Q指向数组对象的最后一个元素之后的一个元素,则表达式(Q)-1指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素之后的一个元素,则评估不会产生溢出;否则,行为是未定义的。如果结果指向数组对象的最后一个元素之后的一个元素,则不能将其用作评估的一元*运算符的操作数。
当你在递增指针时,当你超过数组的末尾时,你会得到一个特殊的“超过末尾”的指针,它会被认为比指向最后一个元素的指针更大,循环将终止。然而,对于递减循环,在达到第一个元素后,你会再次递减指针并导致“下溢”,从而产生未定义的行为。

这几乎是正确的,除了 OP 的前向版本在 final 中使用 p + (length - 1),这在 length == 0 的情况下是未定义行为。 - undefined
谢谢,除了上面的@pmg给出了一个递减循环的解决方案;写成do ... while(q-- > p)。然而,如果length == 0,那个解决方案会有问题,所以应该单独检查一下,我猜... - undefined
@cafce25 对的.. 所以我猜无论是增加还是减少,最好先检查长度是否为0... - undefined
@ Sasha,你可以将条件 q < final 设为 final = p + length; 并进行初始化。此外,FeO3 提供了一个适用于反向情况的版本,无需显式检查 length != 0,甚至适用于不同于 char 类型的情况。 - undefined
@Sasha 当思考事物时,脑海中有一个图像是很好的。例如,一个标准的蛋盒可以容纳0个鸡蛋吗?答案是“空气”... 如果你有一个数组,那么至少有一个元素。 - undefined

1
总结一下大家说的(谢谢!),以下应该总是有效的(我认为):
size_t length = ...;
type_t* p = malloc(length * sizeof(type_t));
type_t* const q = p + length;

/* looping incrementally */
for (type_t* r = p; r < q; ++r) {
  *r = ...;
}

/* looping decrementally */
for (type_t* r = q; r > p;) {
  *--r = ...;
}

1
对于第三个问题,你需要确保for (type_t* r = q; r > p; ) { *--r=...是严格正确的。否则,即使r == p,你也会错误地减小指针,这是未定义的。 - undefined
@ChrisDodd 如果我理解正确的话,我会递减它,但是循环将会结束,所以我将不再关心r的值了... - undefined
1
对于递减的部分...确保 length >= 1(为什么会是0呢?哈哈),并且坚持使用 do ... while 循环 :-) - undefined
@Sasha: 关于“我会递减它,但循环将结束”的问题:不,仅仅依据C标准,你不能确定你会递减它。你尝试使用r-- > p来递减它,但是r--会做什么呢?当r已经等于p,以至于r--会使r指向p之前,C标准规定这种行为是未定义的。所以你并不知道r--会不会递减r。它可能会中止程序。或者编译器可能会根据r--只在r保持在适当范围内时才定义的事实进行推断,并基于这些推断奇怪地优化你的程序。 - undefined
1
@EricPostpischil 好的,非常感谢大家,我很感激。我之前没有意识到仅仅将指针“超出范围”是不应该的。所以现在如果我理解正确的话,代码 char* p = malloc(10); --p; ++p; 是未定义行为,不能保证与 char* p = malloc(10); 相同。但是 char* p = malloc(10); p += 10; p -= 10; 与第二个代码是相同的... - undefined

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