调试技巧(字符串化运算符#)& 变量内存分配问题

Create a program that defines two int arrays, one right after the other. Index off the end of the first array into the second, and make an assignment. Print out the second array to see the changes cause by this. Now try defining a char variable between the first array definition and the second, and repeat the experiment. You may want to create an array printing function to simplify your coding.

Solution:

This exercise asks you to perform operations that are undefined in the C++ language specification, for two reasons:

First, to show that even if an operation is undefined, you can often still go ahead and do it, possibly without any error reports at compile-time or run-time. Thus, coding undefined operations can result in subtle and hard-to-find bugs.

To see what happens when an undefined operation is performed. Since such operations are literally “not defined” in the language, a particular compiler may deal with it in any way that it sees fit. For example, a compiler may actually choose to insert code to prevent you from exceeding the array bounds (or this may be an option that could be switched on and off). Compilers typically do not do this, because it would add extra run-time overhead.

Here’s one way to solve the exercise, with an extra way to stomp on your memory thrown in for good measure:

//: S03:AbusingTheLanguage.cpp
// Shows what happens when you run off the
// end of an array and other misbehavior.
#include <iostream>
using namespace std;

void print(char* name, int* array, int size) {
  for(int i = 0; i < size; i++)
    cout << name << "[" << i << "] (" 
         << (long)(&array[i]) << ") = " 
         << array[i] << endl;
}

// A preprocessor macro to simplify the printing
// of all the data in main():
#define PRT(A, B, C, D) \
  print(#A, A, sizeof A / sizeof *A); \
  print(#B, B, sizeof B / sizeof *B); \
  cout << #C " (" << (long)(&C) << ") = " \
       << C << endl; \
  print(#D, D, sizeof D / sizeof *D);

int main() {
  int a[] = { 1, 2, 3 };
  int b[] = { 4, 5, 6 };
  char c = 'x';
  int d[] = { 7, 8, 9 };
  PRT(a, b, c, d);
  cout << "Index off the end of a:\n";
  a[3] = 47;
  PRT(a, b, c, d);
  cout << "Index off the end of b:\n";
  b[3] = 27;
  PRT(a, b, c, d);
  cout << "Abuse c with pointers and casts:\n";
  *((double*)&c) = 99.99;
  PRT(a, b, c, d);
} ///:~

The print( ) function takes the name of an array, the starting address of the array, and the number of elements in the array (so that the print( ) function itself doesn’t run off the end!), and uses these to display the elements in a readable fashion. In addition, it takes the address of each array element and casts it to along so that it can be displayed. This way you will see the way that your particular compiler allocates storage.

It turns out that it’s helpful – and tedious – to print out all the elements after every change is made in the program. To do this without reproducing a lot of code, the PRT macro is created, which takes four elements (three arrays and one non-array, C) and uses print( ) and other code to display all the details. Note the use of the ‘#’ sign in front of the arguments – this takes the argument identifier and turns it into a string, which is very convenient for printing.

In main( ), 3 arrays are defined one after the other, and the char c is interspersed between the second and third arrays. You might think that this means that the arrays will be laid out in memory in the same order that they are defined, but the compiler is under no such constraint and so you will see different results from one compiler to another. In fact, the compiler that I used (Borland C++ 5.4 on Windows 98) produced the following output:

a[0] (6684152) = 1
a[1] (6684156) = 2
a[2] (6684160) = 3
b[0] (6684140) = 4
b[1] (6684144) = 5
b[2] (6684148) = 6
c (6684139) = x
d[0] (6684124) = 7
d[1] (6684128) = 8
d[2] (6684132) = 9
Index off the end of a:
a[0] (6684152) = 1
a[1] (6684156) = 2
a[2] (6684160) = 3
b[0] (6684140) = 4
b[1] (6684144) = 5
b[2] (6684148) = 6
c (6684139) = x
d[0] (6684124) = 7
d[1] (6684128) = 8
d[2] (6684132) = 9
Index off the end of b:
a[0] (6684152) = 27
a[1] (6684156) = 2
a[2] (6684160) = 3
b[0] (6684140) = 4
b[1] (6684144) = 5
b[2] (6684148) = 6
c (6684139) = x
d[0] (6684124) = 7
d[1] (6684128) = 8
d[2] (6684132) = 9
Abuse c with pointers and casts:
a[0] (6684152) = 27
a[1] (6684156) = 2
a[2] (6684160) = 3
b[0] (6684140) = 1546188226
b[1] (6684144) = 4217087
b[2] (6684148) = 6
c (6684139) = Å
d[0] (6684124) = 7
d[1] (6684128) = 8
d[2] (6684132) = 9

First, notice that even though b is defined after a, it is placed in memory before a. In fact, after noting that ints are 4 bytes long and a char is one byte long (on this machine, with this compiler), the actual order is: d, 3 empty bytes, cb, and then a. Thus, in the initial test that indexes off the end of a, nothing happens because there’s no important memory after the end of a (it’s the “bottom-most” element on the stack). But if you index off the end of b, it moves into a. Again, your compiler will probably lay things out differently and so you’ll see (and have to decipher) different results.

The final example does something that is not advisable but all too common. In C and C++, you can take any variable and pretend it’s another variable using a cast. Here, c (which is one byte long) is actually turned into a variable of type double (8 bytes long). Of course, c is still only one byte long and so if you assign into it as if it were a double, then the other 7 bytes – which happen to belong to b – are written into as well as the one byte of c. Thus, you assign to c and bchanges, which is certainly a bug. On top of that, this program will almost certainly produce different results on different machines, because the memory layout, and often the sizes of the data types, will be different.

When you see this kind of code abuse taking place there will usually be an argument for it, and it might even be convincing (watch for the word “efficiency” which is usually at the root of things). Resist the temptation – there are enough subtle bugs that creep into your code as it is without wantonly introducing them.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值