Journal Articles

CVu Journal Vol 15, #6 - Dec 2003 + Student Code Critiques from CVu journal.
Browse in : All > Journals > CVu > 156 (7)
All > Journal Columns > Code Critique (70)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Student Code Critique Competition

Author: Administrator

Date: 06 December 2003 13:16:02 +00:00 or Sat, 06 December 2003 13:16:02 +00:00

Summary: 

Body: 

The problem

Curious what novices set out to do but that should not stop us from trying to help them achieve their objectives. I suspect that the writer of this is trying to grab the console output and paste it into a file. Now we know what he should be doing, but while helping him try to raise the code quality.

I want to copy my C++ program output to an ASCII text file.

I store my data in arrays and then use cout to output the data in DOS... as I generate data in the order of 1000's ...I am having a problem with physically copy pasting the data into notepad (an MS Windows pure text editor).

Here is my code...please suggest what changes are necessary. My coding is not really that efficient. Please bear with me:

# include<iostream.h>
# include<conio.h>
# include<math.h>
# include<iomanip.h>
void main(){
  void clrscr();
  int i,r,j,m,x;
  static Sum[5000][5000];
  Sum[1][1]=25000;
  Sum[2][1]=35000;
// input the data into arrays
  for(j=2; j<=11; j++)
    for(i=1; i<=pow(2,j); i++) {
      if(i==1) {
        Sum[i][j] = Sum[i][j-1] + 25000;
        Sum[i+1][j] = Sum[i][j-1] + 35000;
      }
      if(i>1) {
        Sum[i][j] = Sum[r][j-1] + 25000;
        r=r+1;
        Sum[i+1][j] =
              Sum[(i+1)/2][j-1] + 35000;
      }
      i=i+1;
      r=2;
    }
// output the data
  for(m=2; m<=11; m++) {
    for(x=1;x <= pow(2,m); x++) {
       for(i=1; i<=2; i++) {
         cout << x << m
              << setw(10) << Sum[x][m] << '\n';
       }
    }
    getch();
  }
}

My output reads:

  11 25000
  11 25000
  12 35000
  12 35000

so on..so forth

My Comments

This time our esteemed editor does not have to spend valuable time offending those whose submissions are considered inferior to the winner's because there was only one submission, which by definition, has to be the winner. That is sad because I guess that many more of you could have had a go. Yes, many winners write well but so do many who do not win and as I have written before, it is participating that is the biggest reward. In order to provide an alternative I asked my friend who writes as the Harpist to have a look and provide his thoughts.

The Harpist's Response

I notice that in setting the problem Francis asked that we focus on helpful advice to the author. So here goes:

A Clear Statement of Intent

Before writing a single line of code you need to have a clear idea about what you are trying to produce. In this case it seems that the objective is to produce a file of values generated by some form of generator. I am certain that the actual code does not do what the programmer thinks because it is littered with quirky bits. If it did actually generate the desired values it would be pure chance.

Understand Your Tools

Fundamentally there are two ways to create an output file. The first way is to have the program create the required file. The second way that is often forgotten is to simply capture the program output into a file. What you do not do (or only very rarely) is to capture the output from the screen and paste it into a file. It seems that the student has been trying to use this last method.

Write Portable Code

There are times when we have to fall back on implementation specific features and library functionality but they are much less common than most students believe.

Never Do Things Till You Have To

That almost speaks for itself so I will just leave it as a thought to put alongside 'Do things in time.'

Applying These Principles

Here is my rewritten code up to the student's first comment:

# include<iomanip>
# include<iostream>
int main() {
  static int sum[5000][5000];
  sum[1][1]=25000;
  sum[2][1]=35000;

Two of the header files go. We do not need conio.h because we are not going to use the screen as some intermediary for getting the output where we want it. Looking forward I realise that the programmer only needed math.h to give him access to the declaration of pow(). Using that function to compute successive powers of two is overkill. It is the kind of overkill that inexperienced programmers are very prone to. They think 'power of two' and forget that what they needed was 'next power of two' which is simply two times the current one.

While I was about it I replaced the header files with the equivalent Standard C++ headers.

Next I tidied up the declaration of main() to return the required int, and threw out the definitions of i etc. because this is far too early to be declaring single letter variables (I have nothing against them but they should always be declared in the context of their use.) Even C now allows that. clrscr() went because it serves no useful purpose.

I think the student was getting away with an implicit int in the declaration of Sum, which I have amended to sum because I cannot abide gratuitous use of upper case. In case you are wondering, removing the static storage class specifier would result in undefined behaviour because the array would not have been initialised. Of course there are other ways of ensuring that an array is fully initialised.

In passing, I feel profoundly uncomfortable with that requirement for storage for twenty-five million ints. I am sure that a correct analysis of the required computation will demonstrate that a much less memory demanding approach is viable.

Now let me look at the computation section. This definitely can do with some restructuring because, frankly, it is currently a hacked together mess.

int i_limit = 4;
for(int j=2; j != 12; ++j, i_limit *=2) {
  sum[1][j] = sum[1][j-1] + 25000;
  sum[2][j] = sum[1][j-1] + 35000;
  for(int i=3; i<= i_limit; i += 2) {
    sum[i][j] = sum[2][j-1] + 25000;
    sum[i+1][j] = sum[(i+1)/2][j-1] + 35000;
  }
}

Where has the r gone? Careful analysis of the control flow of the original code showed that r was always 2 when used. I find that very suspicious and think that it probably should have been equal to i. However the point I would make to the student is that by cleaning up the code we can see what is happening. This will make it much easier to correct errors in the code. That '2' where r was sticks out like a sore thumb. Indeed I suspect that the correct code should be:

int i_limit = 4;
for(int j=2; j != 12; ++j, i_limit *=2) {
  for(int i=1; i<= i_limit; i += 2) {
    sum[i][j] = sum[i][j-1] + 25000;
    sum[i+1][j] = sum[(i+1)/2][j-1] + 35000;
  }
}

Once we have it down to this level, we can see that the original dimensions of sum are ridiculous. In fact he has 10 sets of results, each set solely dependant on the immediately prior set. Allowing indexing from 1 rather than 0 and not even attempting to optimise storage we get:

static int sum[2049][12];

as being entirely sufficient. This reduces the storage requirement to something reasonable on most hardware (around about a hundred thousand bytes on a platform using 4-byte ints.

Finally let me look at the output section. This also has its oddities. Here is my rewritten version:

i_limit = 2; // reset to initial value
for(int j=1; j != 12; ++j, i_limit *= 2) {
  for(int i=1; i <= i_limit ; ++i){
    std::cout << i << j << std::setw(10)
              << sum[i][j] << '\n';
    std::cout << i << j << std::setw(10)
              << sum[i][j] << '\n';
  }
}

Now why the student wants to output the values of i and j without an intermediate space, and why he wants to output each result twice will for ever remain a mystery but the process of cleaning up his code makes it quite clear that that is exactly what he does. By the way, for consistency I changed the output loop so that it will display the initial data as well as the computed data.

Now I suspect that all the code mangling came about while the student struggled to solve the problem of getting the output into a text file. His mind was so focused on trying to cope with this that everything else went up in flames. So how to get the data into a file? Simply, invoke the program with:

program > data.txt

Which I think makes it clear that the student actually needed a very different answer from the one he was asking for.

Student Code Critique 24 from Roger Orr

"Hmm", said Bill, "I wonder what this program does. Any ideas, Jim?"

Jim came over and stared at Bill's screen. "Well", he said, "all the user wants is to be able to copy the output to an ASCII text file. Does it matter what it does?"

Bill thought a moment and then said, "Yes, it does matter - partly for professional pride but also to try and understand what he means here where he writes 'my coding is not really that efficient'".

Jim looked puzzled and tentatively suggested that it didn't matter - surely the code was just a bit slow and so what it was doing was irrelevant since you obviously don't need to understand the purpose of code to make it faster.

"No, I'm afraid you've missed the point of my remark- efficiency is not necessarily anything to do with speed. In this case the user is currently pasting data into notepad - the speed of the program is a tiny part of the overall time. I suspect the user is actually worried about either the inefficient use of memory or the amount of effort it took turn the original formula into code. Look, I'll show you what I mean."

Bill turned to the keyboard, compiled the sample code and ran the program. There was a long pause.

"You're wrong", said Jim, "look how long it's taking to get anywhere - what is the program doing?" Bill simply pointed to the CPU meter, which was showing almost zero, and said "No, it's not busy - what is it waiting for? …Ah of course, silly me."

With that he leaned on the space bar and almost immediately the screen filled with numbers.

"The problem is the mix of I/O since the output uses the C++ streams library - well, an old one anyway - which buffers up output to write many characters at once for speed, but the input uses a non-standard low-level C runtime call. It isn't a good idea to pair up buffered output and unbuffered input. It looks like the user really wanted to all the pending output to appear each time before waiting for a keypress. Oh well, so much water under the bridge - we want to write the entire output to an ASCII text file now so there's no need to wait - I'll remove the getch() call and the #include of conio.h".

Bill applied himself to the keyboard, and made these two changes. He then noticed that the 'clrscr()' function was not in fact needed, so removed that line too. "Just reversing entropy", he muttered to himself, "gets in everywhere doesn't it?"

He then decided to bring the program into the world of the C++ standard. This meant making 'void main()' become 'int main()' and then changing the old includes 'iostream.h' and 'iomanip.h' to the new ones 'iostream' and 'iomanip'. Of course, the standard C++ streams library is in the namespace 'std' so he had to added 'std::' before 'cout' and 'setw'.

"Ok, let's try that", he said, "Oh … our output doesn't match what the user says he gets - odd. Perhaps I've broken something….no - look - the output loop starts with m=2 but the user's output starts with '11'. Looks like the code he sent us doesn't match what he's actually using. Not the first time, eh? Now we really do need to know what the program does so we can decide whether the program or the sample output is wrong - or both of course! Ok, for now we'll change the second loop to start with m=1 and now the results do look like what the user wants. Hey, look here - this variable 'r'. The first time round the input loop it isn't used, just set to 2. All the other times round the loop it is used, then incremented, and then reset to 2. I wonder if that is really right? For now I'll put a warning comment in the margin."

"Right, now we're ready. Here goes". Bill opened a command prompt and typed:

scc24 > x.txt

"That's got the output into a text file", he said.

"Isn't that cheating - doesn't the user want to do that with C++?", Jim objected.

"He is using C++", said Bill, "and we're simply using the features of his chosen environment. KISS."

"Pardon??"

"Keep It Simple, Stupid", explained Bill, "Why write code when you don't have to? Let the operating system solve that part of the problem! Now let's send the user the tidied-up program and instructions on how to run it."

The Winner of SCC 24

The editor's "choice" is:

Roger Orr

Please email to arrange for your prize.

Student Code Critique 25

(Submissions to by Jan. 14th)

It is always worth sending in a late entry, depending on my workload it may or may not be considered for the prize but it will eventually get published.

This time I am presenting two short C programs. Please attempt to critique either or both of them.

Program 1

I am little confused about return values of sizeof operator. Here is a simple C program which is putting me in doubt:

#include <stdio.h>
int main() {
  int a[10];
  int *p =(int *) malloc (10);
  printf("Size of a = %d \n",sizeof(a));
  printf("Size of p = %d \n",sizeof(p));
}

Output is :

Size of a = 40
Size of p = 4

My understanding says even array name is a pointer. If so why it does not show sizeof(a) as 4? Or if sizeof shows the total allocated memory then why sizeof(p) does not show 10?

There are numerous errors in both the code and the student's understanding. Please address these comprehensively, perhaps including places where your explanation would be different if this were a C++ program.

Program 2

I am getting an error linker error. Here is the code:

// Define Libraries
#include <stdio.h>
#include <math.h>
//Start Of Main Program
main(){
  double hypo, base, height;
/* Enter base and height */
  printf("Enter base:");
  scanf("%f", &base);
  printf("Enter height:");
  scanf("%f", &height);
  hypo = sqrt(pow(base, 2)+pow(height, 2));
  printf("hypotenuse is %f", &hypotenuse);
}

Ignore the question asked by the student and address the serious problems with the code itself.

Notes: 

More fields may be available via dynamicdata ..