问题描述
算法题:给定一个字符串,逐个翻转字符串中的每个单词,例如输入是"I am a student in Renmin University of China”,输出"China of University Renmin in Student a am I".(用栈实现)
算法思想:运用俩个栈实现一个特殊队列,遇到空格则将栈1的元素入到栈2里。
先看代码
#include <malloc.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
#define OVERFLOW -2
#define OK 1
#define NO 0
#define STACK_INIT_SIZE 100
#define STACKINCREMENT 10
typedef int ElemType;
typedef struct SqStack
{
char *base;
char *top;
int stacksize;
} SqStack;
void Init_SqStack(SqStack &S)
{
S.base = (char *)malloc(sizeof(char));
if (!S.base)
exit(OVERFLOW); //分配失败
S.top = S.base;
S.stacksize = STACK_INIT_SIZE;
return;
}
void push(SqStack &S, char e)
{
if (S.top - S.base >= S.stacksize) //栈满追加空间
{
S.base = (char *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(char));
if (!S.base)
exit(OVERFLOW);
S.top = S.base + S.stacksize;
S.stacksize += STACKINCREMENT;
}
*S.top++ = e;
return;
}
void Pop(SqStack &s, char &e)
{
if (s.base == s.top)
{
cout<<'error'<<endl;
return ;
}
e = *--s.top;
return ;
}
void showstack(SqStack s)
{
char *p=s.base;
int len=s.top-s.base;
for(int i=len;i>=1;i--)
{
char a=*(p+i-1);
cout<<a;
}
cout<<endl;
}
int isempty(SqStack s)
{
int a=0;
if(s.base==s.top)
{
a=1;
}
return a;
}
void spread(SqStack &s1, SqStack &s2)
{
char e;
while(!isempty(s1))
{
Pop(s1, e);
push(s2, e);
}
return;
}
void clear_stack(SqStack &s)
{s.top=s.base;
free(s.base);
s.stacksize=0;
return;
}
void reverse_words(char s[])
{
SqStack result, tmp;
Init_SqStack(result);//用于储存输出结果的栈
Init_SqStack(tmp);//暂时存储中间过程的栈
int i = 0;
while (s[i] != '\000')
{
if (s[i] != ' ')
{
push(tmp, s[i]);
}
else if (s[i] == ' ')
{
push(tmp,s[i]);
spread(tmp,result);//将tmp里的元素出栈,在入result栈
}
i++;
}
push(tmp,' ');
spread(tmp,result);//处理最后一个单词
char e='0';
while(!isempty(result))
{
Pop(result,e);
cout<<e;
}
clear_stack(tmp);
clear_stack(result);
return;
}
int main()
{
char s[100] = {0};
gets(s);
reverse_words(s);
return 0;
}
问题出现
这是正常运行的结果
这是 调试时候的输出结果
可以发现俩种途径得到输出不一样(人傻了)
后面重新改了一下reverse函数中的一部分发现运行结果和调试结果都是正确的,开始四处询问????????????????????
int isblank=0;//判断是否遇到空格
while(s[i]!='\000')
{
if (s[i] != ' ')
{
push(tmp, s[i]);//遇到字母入栈
i++;
}
else if (s[i] == ' ')//遇到空格表明一个单词已经入栈完
{ isblank=1;
push(tmp, ' ');
i++;
}
if(isblank==1)//遇到空格将单词出栈进入结果栈内
{
spread(tmp,result);
isblank=0;
}
}
push(tmp,' ');
spread(tmp,result);//处理最后一个单词
然后关于错误代码,尝试输出每次spread时候俩个栈的状态
以及找了一些例子测试
发现不管单词长短,凡是到了32位就发生了内存问题。
问题解决
百思不得其解,后经过老师点拨发现问题。
我们回到初始化代码,貌似也没有问题。
仔细想了一下,俩次malloc(1)可能会引起内存问题。
我们发现c语言中俩次malloc(1)地址竟然相差32,这实际上是不同编译器/标准libriaries之间的实现细节。通常,无法保证随后的malloc
调用将返回相邻的内存区域。另请注意,每个内存区域都与其他元数据相关联,包括但不限于区域大小(否则,当运行free
时,无法知道要释放多少内存)。此元数据通常放在已分配的区域内,使其大于请求的大小。还有一些其他因素影响实际分配的内存量 - 内存对齐和malloc
实现使用的“漏洞”查找算法等。详见:jkt's blog: Tagged pointers, and saving memory in Trojita
解决方法:
1.通过在sizeof前乘以一个大于32的整数(一般选用50或者100),问题得到解决。
2.直接使用new来初始化空间。
debug和直接运行出现差别的分析
一.参考DEBUG模式下, 内存中的变量地址分析 - findumars - 博客园
试验结论 - <<DEBUG模式下, 内存中的变量地址分析>>
- 如果定义多个变量
- DEBUG模式下, 内存中的变量地址和定义的顺序相同
- 变量开始地址都是模式地址
- 先定义的变量在内存高地址
- 后定义的变量在内存低地址
- Release模式下, 经过优化, 变量的内存地址和变量定义的顺序不相同!
- 如果变量有越界访问的情况, Release模式下的越界访问情况未知.
- 好像有一些规律, 依赖于编译器的优化选项
- 依赖变量越界访问达成变量的存取, 在Release模式下会和Debug模式下的运行效果不相同.
- 运行结果肯定不对了. 是否报错,要看运气了. 如果不报错, 发布后会死的很惨.
所以,之前 修改reverse函数后Debug和Release最后结果一致可能只是运气问题,不一定真的正确。
二.参考使用malloc遇到的奇怪问题——调试的时候正确,运行的时候结果就不对了 - Ash_boy - 博客园
稍微仔细一点的同学都能发现p = (struct A*)malloc(sizeof(struct A*));是不是看起来有点别扭?解释解释?首先malloc函数申请一块sizeof(struct A*)这么大的内存,然后将返回类型强制转换为struct A型的指针赋值给p。那么sizeof(struct A*)是多大呢?熟悉指针的人都知道一般是4个字节,实在不知道的话,进入调试状态在watch窗口输入sizeof(p),因为p为struct A*,其大小即struct A*大小。
问题已经非常明显了,我们要动态开辟一个struct A型的p,那么开创的空间肯定要和struct A一样大的,4字节够了吗?原来是多了一个*。更正以后运行,正确无误了。
问题是比较简单的,也是非常容易解决的。
但是,如果你忘记了free(不会报任何错误),而且你有将动态分配的p进行了很多其他的操作的时候,或者是在其他地方也动态开辟了内存的话。那么你会发现怎么这些数据都非常的奇怪,居然是凭空产生的,非常难以理解。明明是整个的思路,逻辑,语法都没错却有这样奇怪的结果。再回到前面的问题,我们只给p分配了4个字节,那么他是怎么访问结构体里面的100个int型的数据呢?这就不的不提起数组越界了。C是不对越界进行检查的,所以p中就如上面所提到的正确访问,正确赋值,正确输出。但是它访问的并不是它所拥有的空间,也就是其他变量的,如果其他变量进行其他操作的话,它的值也就变了。至于怎么变就不知道了。
debug模式的malloc
参考debug模式的malloc_馋嘴猪没胃口-CSDN博客
我们每次申请一块内存,都会有额外的内存被分配,所以使用连续使用malloc可能会导致内存溢出。