我正在阅读Herbert Schildt的C ++参考书,本书的c部分有一个堆栈示例,类似于以下内容:
#include <stdio.h>
#include <stdlib.h>
#define SIZE 50
void push(int i);
int pop(void);
int *tos, *p, stack[SIZE];
int main(void) {
tos = stack;
p = stack;
// push, pop, etc
return 0;
}
void push(int i) {
p++;
if (p==(tos+SIZE)) {
printf("Stack overflow");
exit(1);
}
*p = i;
}
int pop(void) {
if (p==tos) {
printf("Stack Underflow");
exit(1);
}
p--;
return *(p+1);
}
上述堆栈实现不使用TOS存储值。第一个值存储在TOS + 1。我不明白为什么你会这样做,因为它似乎浪费了一个记忆空间。
我已将其重写为下面的示例,这不会浪费空间(编辑:阅读所选答案 - 下面的代码是可疑的!):
#include <stdio.h>
#include <stdlib.h>
#define SIZE 5
void push(int i);
int pop(void);
int *tos, *p, stack[SIZE];
int main(void) {
tos = stack;
p = stack;
// push, pop, etc
return 0;
}
void push(int i) {
if (p==(tos+SIZE)) {
printf("Stack overflow");
exit(1);
}
*p = i;
p++;
}
int pop(void) {
p--;
if (p<tos) {
printf("Stack Underflow");
exit(1);
}
return *p;
}
Schildt的实施是否有理由不使用TOS职位?
非常感谢。
答案 0 :(得分:6)
原始代码虽然风格不佳,却有明确的行为。修改后的代码行为未定义。
要测试堆栈是否为空,您需要在修改指针之前进行指针比较。如果比较表明堆栈为空,不会递减指针。
如果p
指向stack[0]
,则p--;
具有未定义的行为,即使您之后从未取消引用p
。你不能合法地形成一个指向数组开头之前的指针,或超过一个结束的指针。 (你可以合法地在数组的末尾形成一个指针,但你不能取消引用它。)
它可能在大多数基于堆栈的实现中“起作用” - 这只是意味着这是一个很难通过测试找到的错误。
原始代码中的问题:tos
最初指向stack
的元素0,并且push()
或pop
()未对其进行修改。为什么不将它声明为const
,为什么它被称为tos
(一个暗示“堆栈顶部”的名称,但这就是p
用于的内容),以及为什么有这个变量毕竟,假设stack
或&stack[0]
给你相同的价值?我怀疑这些问题是否有任何好的答案。
同样在原始代码中,exit(1);
不是一种信号失败的可移植方式(有些系统可以表示成功)。 exit(EXIT_FAILURE);
是正确的方法,除非你是故意写不可移植的代码。 (如果您编写的程序仅用于类Unix系统,那么这是有意义的,但如果您正在为语言教程编写示例,则不行。)
我的建议:谷歌“Schildt”要了解作者作为C和C ++书籍作者的极差,然后找一本真正理解该语言的人写的书。
(这个答案的先前版本错误地将Schildt归咎于一些不是他的错误;我为此道歉。我仍然建议避免他的书。)
答案 1 :(得分:1)
看起来他正试图让p总是指向堆栈中的某个位置,而不是在像你这样的堆栈之前指向内存中的某个随机位置。只要无效的p在任何地方都没有被解除引用(即,如果它是错误的,那么你就会调用exit),那应该没什么区别。