[关闭]
@fcxxzux 2017-01-01T13:17:59.000000Z 字数 2740 阅读 1088

Learning a Prat of C++(for ACM/ICPC) (extended 1) 那些年踩过的坑

这里整理一下一些经典的、能让你调试老半天的大坑。
(在前面出现过的,就不重复提了。)

1、

  1. int i=0;
  2. int f(){
  3. i*=2;
  4. return i;
  5. }
  6. int g(){
  7. i+=1;
  8. return i;
  9. }
  10. int main(){
  11. printf("%d\n",g()+f());
  12. }

你们觉得结果是多少?
事实上,这个结果是不确定的!根据编译器的不同,可能会先执行g()再执行f(),也可能会先执行f()再执行g()。
也可以参见:https://www.zhihu.com/question/34787444/answer/60930197
或者买本《C++程序设计陷阱》,陷阱14就包含这个问题。

2、
51nod上的题:
https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1376
有人写了代码,思路很正确,可是跑起来就是不对头。
(提示:注意第76行)

  1. #include<stdio.h>
  2. #include<string.h>
  3. #include<stdlib.h>
  4. #include<time.h>
  5. #include<vector>
  6. #include<algorithm>
  7. #define MAXN 50003
  8. using namespace std;
  9. const int P=15954746;
  10. const int A=16233459;
  11. const int BG=1000000007;
  12. const int INF =0x3f3f3f3f;
  13. int dp[MAXN];
  14. int data[MAXN];
  15. int f[MAXN];
  16. int H[MAXN];
  17. int U=A;
  18. int Frand()
  19. {
  20. srand((U+A)%P);
  21. U=rand()%P;
  22. return U;
  23. }
  24. struct point
  25. {
  26. int key;
  27. int c[3];
  28. int H;
  29. int h;
  30. int pri;
  31. point ()
  32. {
  33. pri=Frand();
  34. c[1]=c[0]=0;
  35. key=h=H=0;
  36. }
  37. point(int key,int pri,int h,int H):key(key),pri(pri),h(h),H(H)
  38. {
  39. c[1]=c[0]=0;
  40. }
  41. };
  42. struct Treap
  43. {
  44. vector<point>D;
  45. int root;
  46. Treap ()
  47. {
  48. root=0;
  49. D.push_back(point(0,INF,0,0));
  50. }
  51. void updata(int x)
  52. {
  53. D[x].H=( ( D[D[x].c[0]].H + D[D[x].c[1]].H ) % BG+ D[x].h ) % BG ;
  54. }
  55. int rotate(int x,int t)
  56. {
  57. int y=D[x].c[t];
  58. D[x].c[t]=D[y].c[1-t];
  59. D[y].c[1-t]=x;
  60. updata(x);
  61. return y;
  62. }
  63. int _insert(int x,int k,int h)
  64. {
  65. if(x)
  66. {
  67. if(D[x].key==k)
  68. {
  69. D[x].h=(D[x].h+h)%BG;
  70. }
  71. else
  72. {
  73. int t=D[x].key<k;
  74. //int res=_insert(D[x].c[t],k,h);
  75. //D[x].c[t]=res;
  76. D[x].c[t]=_insert(D[x].c[t],k,h);
  77. //printf("-%d(%d %d) ",D[x].c[t],D.capacity(),D.size());
  78. if(D[D[x].c[t]].pri<D[x].pri) x=rotate(x,t);
  79. }
  80. }
  81. else
  82. {
  83. x=D.size();
  84. //printf("%d(%d %d)",x,D.capacity(),D.size());
  85. D.push_back(point(k,Frand(),h,h));
  86. }
  87. updata(x);
  88. return x;
  89. }
  90. int _Query(int x,int k)
  91. {
  92. if(x==0) return 0;
  93. if(D[x].key==k)return D[D[x].c[0]].H;
  94. if(D[x].key<k) return ( ( D[D[x].c[0]].H + D[x].h ) % BG+ _Query ( D[x].c[1] , k ) )% BG;
  95. return _Query ( D[x].c[0] , k);
  96. }
  97. void insert(int k,int h)
  98. {
  99. root=_insert(root,k,h);
  100. }
  101. int Query(int k)
  102. {
  103. return _Query(root,k);
  104. }
  105. }T[MAXN];
  106. int main ()
  107. {
  108. memset(dp,0x3f,sizeof(dp));
  109. srand((time(NULL)+A)%P);
  110. U=rand()%P;
  111. int N,len=1;
  112. scanf("%d",&N);
  113. for(int i=0;i<N;i++)
  114. {
  115. scanf("%d",data+i);
  116. f[i]=lower_bound(dp+1,dp+N+1,data[i])-dp;
  117. dp[f[i]]=data[i];
  118. len=max(len,f[i]);
  119. }
  120. int sum=0;
  121. T[0].insert(-INF,1);
  122. for(int i=0;i<N;i++)
  123. {
  124. H[i]=T[f[i]-1].Query(data[i]);
  125. if(f[i]==len) sum=(sum+H[i])%BG;
  126. else T[f[i]].insert(data[i],H[i]);
  127. }
  128. printf("%d\n",sum);
  129. return 0;
  130. }

样例数据:

  1. 5
  2. 1 3 2 0 4
  3. 应该输出
  4. 2

然后发现Treap的插入操作好像并没有正确执行。
然后发现第76行改成第74、75行就行了。
为什么?

这是前面例子的一个更隐蔽的版本。
vector的下标运算符也算是函数调用。
对于D[x].c[t]=_insert(D[x].c[t],k,h);,编译器发现有:赋值运算符前面的D[x].c[t]、_insert中的D[x].c[t]、_insert()函数自己要结算。
显然_insert要在_insert中的D[x].c[t]算出来之后再执行,那么,2个D[x].c[t],他们要按照什么顺序结算呢?
答:因为不存在相互依赖的关系,什么顺序都行。
C++里,这种子表达式的求值顺序,是不确定的。

于是有的编译器先算好了赋值运算符前面的D[x].c[t](的地址)。
然后灾难在后面的递归调用中的push_back()。
push_back()修改了vector。而vector增加东西,如果发现剩余空间不够,在内部,会new一块新数组,老的数组就不要了。
这时候,先计算的 赋值运算符前面的D[x].c[t](的地址)其实已经是废弃的地方了,函数返回了,再往里面写入,就显得没有效果了。

——所以,不要偷懒,多写几句,省下你自己不知道多少debug时间。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注