?
Solved

For Loop Question

Posted on 2007-10-15
15
Medium Priority
?
199 Views
Last Modified: 2010-04-15
What does the second for loop do?  Specifically "*p", how does that for loop knows when to stop?

#include <stdio.h>
int main (int argc, char *argv[])
{
  int i,n;
  char *p;
  printf("argc is: %d\n", argc);

  for(i=0;i<argc;i++)
  {
    for(p=argv[i],n=0; *p;n++,p++);
    printf("\n%s %d",argv[i],n);
  };
}
0
Comment
Question by:xp5
  • 8
  • 5
  • 2
15 Comments
 
LVL 85

Expert Comment

by:ozo
ID: 20082369
the loop stops when *p is false, i.e. 0
0
 
LVL 11

Expert Comment

by:lbertacco
ID: 20083138
The first loop loops over all arguments passed to the program. The second loop loops over all chars of a given argument counting them (and then it print the argument and its length).
for example if you run the program (say test.exe) as
test hi
it would print
test 4
hi 2
In other words it does the same as:

#include <stdio.h>
int main (int argc, char *argv[])
{
  int i;
  printf("argc is: %d\n", argc);
  for(i=0;i<argc;i++) printf("\n%s %d",argv[i],strlen(argv[i]));
}
0
 
LVL 39

Accepted Solution

by:
itsmeandnobodyelse earned 2000 total points
ID: 20083767
>>>> What does the second for loop do?  
>>>> Specifically "*p", how does that for loop knows when to stop?
That is the kind of loop C is 'beloved' or 'hated' for ...

Let's look at all parts of the for statement:

>>>>   p=argv[i]

pointer p was set to the i'th argument passed at the commandline. Arguments start with index 1. argv[0] is the name of the modul (program) started.

If a char array was assigned to a pointer, the pointer points to the first char element of that array.

>>>> ,n=0;

With , you can concatinate statements. n is the counter of that loop which main purpose is to determine the string length of the argv[i] argument.

>>>> *p;
As told p is pointing to an element of a char array. So, *p is the single character it is pointing to. The *p is the conditional part of the for loop. The condition *p is false if *p is 0 what means if it is the terminating zero-character of a char array string. All arguments passed in argv array are zero-terminated strings.

>>>> n++
increments the loop counter

>>>> ,p++
increments the pointer p so that it points to the *next* character of argv[i] string.

Note, if the argument was an empty string (you could get it if passing "" as argument), the condition *p was false at the very begin. Then p points to the begin of an empty string what is the terminating zero character and n = 0.

>>>> for(p=argv[i],n=0; *p;n++,p++);

  n = strlen(argv[i]);

is an equivalent statement (and should be preferred in case you don't need to spare any nanosecond possible).

Regards, Alex




0
Concerto Cloud for Software Providers & ISVs

Can Concerto Cloud Services help you focus on evolving your application offerings, while delivering the best cloud experience to your customers? From DevOps to revenue models and customer support, the answer is yes!

Learn how Concerto can help you.

 
LVL 85

Expert Comment

by:ozo
ID: 20083802
for(p=argv[i],n=0; *p;n++,p++);
is equivalent to
p=argv[i],n=0; while( *p ){ n++,p++}
0
 
LVL 11

Expert Comment

by:lbertacco
ID: 20084223
Well if you need to spare any nanosecond, the code
>>>> for(p=argv[i],n=0; *p;n++,p++);
is surely not the fastest idea to go with. For example
  for(p=argv[i];*p;p++);
  n=p-argv[i];
is already twice as fast.
And you can get even faster using the assembly SCAN instruction.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20085101
>>>> is already twice as fast

Running both the loops a billion of times (using a 10 bytes string value) gives the times below (milliseconds on a W2k, 3.0 P4)


for(p=argv[0],n=0; *p;n++,p++)        10594
for(p=argv[i];*p;p++)n=p-argv[i];      9312

So, it is only 12% advantage.

0
 
LVL 11

Expert Comment

by:lbertacco
ID: 20085230
Well if you really tested that code, I"m surprise it is any faster at all.
In fact, you missed a ";" between "...p++)" and "n=...". Try with
for(p=argv[i];*p;p++); n=p-argv[i];  
and eventually with longer strings ;-)
I'm just curious. It's not twice as fast, you are right. It makes half integer increments but still it does 1 increment, 1 dereference and a test per cycle versus 2 increments, 1 dereference and a test.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20085279
>>>> In fact, you missed a ";" between "...p++)"
It is only a printout.

Adding the strlen it gives

for(p=argv[0],n=0; *p;n++,p++);        10610
for(p=argv[0];*p;p++);n=p-argv[0];      10547
n=strlen(argv[0]);                     44437

what means that this time the differences between both the loops were < 0.1 ns and about 34 ns between strlen and either loop.

0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20085311
Running it a few times I got as well the first loop and the second loop best. Differences were from +300 to -1500.
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20085367
Here the test code:
#include <windows.h>  // GetTickCount();
int main(int argc, char* argv[])
{
    int start = 0;
    int end = 1000000000;  // 1 billion  
    int c1 = 0;
    char* p;
    int n;
    int sig = 1;
    long l0 = GetTickCount();
    {
        for (int i = start; i < end; ++i)
        {
            for(p=argv[0],n=0; *p;n++,p++);
            c1 += n * sig;
            sig = -sig;
        }
    }    
    int c2 = 0;
    long l1 = GetTickCount();
    {
        for (int i = start; i < end; ++i)
        {
           for(p=argv[0];*p;p++);
            n=p-argv[0];
            c2 += n * sig;
            sig = -sig;
        }

    }
    long l2 = GetTickCount();

    int c3 = 0;
    {
        for (int i = start; i < end; ++i)
        {
            n=strlen(argv[0]);
            c3 += n * sig;
            sig = -sig;
        }

    }
    long l3 = GetTickCount();

    cout << "for(p=argv[0],n=0; *p;n++,p++);   " << setw(10) << right << l1 - l0 << setw(10) << right << c1 << endl;
    cout << "for(p=argv[0];*p;p++);n=p-argv[0];" << setw(10) << right << l2 - l1 << setw(10) << right << c2 << endl;
    cout << "n=strlen(argv[0]);                " << setw(10) << right << l3 - l2 << setw(10) << right << c3 << endl;
    return 0;
}

The c1, c2, c3 is to prevent from optimizing the outer loop.
0
 
LVL 11

Expert Comment

by:lbertacco
ID: 20085937
Even more out of topic and time wasting :-)

I modified your program to split the 3 versions inside 3 different functions, so that they can get optimized in the same way. I have added a test on the output value (otherwise the optimizer can figure out that it doesn't really need to compute the string length as that value is never used) but removed other computations on the result otherwise those can alter the results. See code below.
Compiling in debug mode and running it on a 30char long string I get:
for(p=s,n=0; *p;n++,p++);  11062
for(p=s;*p; p++);n=p-s;        11672
n=strlen(s);                         2234
so I was wrong. But if I change from for(p=s;*p; p++);n=p-s;  to for(p=s;*p++;);n=p-s-1;
I get
for(p=s,n=0; *p;n++,p++);   10984
for(p=s;*p++;);n=p-s;          9688
n=strlen(s);                         2234
and in this case the second version wins over the first one by about 10%. And strlen is much faster.
In release/optimized mode, checking the assembly output I can see that the optimizer has figured out that it doesn't really need to increment both n and p in the first version, so they differ much less:
for(p=s,n=0; *p;n++,p++);         3422
for(p=s;*p++;);n=p-s-1;      3297
n=strlen(s);                      3359
The second version wins again (by a smaller margin), however I haven't found out why now strlen is slower than its debug counterpart (which was the overall winner).

#include <windows.h>  // GetTickCount();
#include <iostream>
#include <iomanip>  
using namespace std;

#define ITER 100000000

void f1(char *s)
{
    char* p;
    int n;
    for (int i = 0; i < ITER; ++i) {
       for(p=s,n=0; *p;n++,p++);
       if(n==0) exit(0);
    }
}

void f2(char *s)
{
    char* p;
    int n;
    for (int i = 0; i < ITER; ++i) {
       for(p=s; *p++;);
       if(p-s-1==0) exit(0);
    }
}


void f3(char *s)
{
    for (int i = 0; i < ITER; ++i) {
      if(strlen(s)==0) exit(0);
    }
}
   
int main(int argc, char* argv[])
{
  long l0 = GetTickCount();
  f1(argv[1]);
  long l1 = GetTickCount();
  f2(argv[1]);
  long l2 = GetTickCount();
  f3(argv[1]);
  long l3 = GetTickCount();
  cout << "for(p=s,n=0; *p;n++,p++);   " << setw(10) << right << l1 - l0 <<  endl;
  cout << "for(p=s;*p++;);n=p-s-1;" << setw(10) << right << l2 - l1 <<  endl;
  cout << "n=strlen(s);                " << setw(10) << right << l3 - l2 <<  endl;
  return 0;
}
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20086845
>>>> Compiling in debug mode
Actually you can't make any statements if running in debug mode. Especially if measuring statements which were compiled to a few assembler operations only.

I compiled it in release mode only and got these results:

for(p=s,n=0; *p;n++,p++);         4234
for(p=s;*p++;);n=p-s-1;      4219
n=strlen(s);                      7172

what shows that my C runtime is slower than your's.

>>>> #define ITER 100000000
it is only 100 millions not a billion so we have to multiply your times by 10. They can be explained that in my sample I used a 7 bytes string while your's was about 30 char.  Setting ITER to 1,000,000,000 and passing a 7 bytes argument I got

for(p=s,n=0; *p;n++,p++);         8891
for(p=s;*p++;);n=p-s-1;      9843
n=strlen(s);                     44485

what is pretty much the same as above though it is not quite clear why the first loop now always is better.

Changing the functions by macros, e, g.

#define f1(s, c) \
{\
    char* p; \
    int n;\
    for (int i = 0; i < ITER; ++i) {\
       for(p=s,n=0; *p;n++,p++);\
       if(!(c = n)) exit(0);\
    }\
}

#define f2(s, c)\
{\
    char* p;\
    for (int i = 0; i < ITER; ++i) {\
       for(p=s; *p++;);\
       if(!(c = p-s)) exit(0);\
    }\
}

#define f3(s, c)\
{\
    for (int i = 0; i < ITER; ++i) {\
      if(!(c = strlen(s))) exit(0);\
    }\
}
   
Note, I passed a second variable which I later used in the output statements, but it doesn't give any difference:



 
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20086857
>>>> Changing the functions by macros,

... doesn't make changes either.
0
 
LVL 11

Expert Comment

by:lbertacco
ID: 20086965
Macros don't make any difference because the compiler is inlining the functions anyway in release mode  (so it achieve the same result as your macros) and by the way the functions are called just 3 times overall , not 3 times 1 billion so you cannot notice the time difference.

The reason that the first version wins with short strings is because the second version has a faster inner cycle but needs to calculate a subtraction at the end. So , if the loop is short, the subtraction cost can exceed the time saved during the loop. The longer the strings, the faster the second version gets (compared to the first version)

Still can you see if your strlen is faster in debug mode (your strlen in release mode is surprisingly slow)?
I'll stop here because I don't want to annoy to much everybody else...
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 20087096
>>>> The reason that the first version wins with short strings is
It doesn't win. It only won with my code (sometimes).

>>>> can you see if your strlen is faster in debug mode
for(p=s,n=0; *p;n++,p++);        30390
for(p=s;*p++;);n=p-s-1;     27125
n=strlen(s);                     16750

Yes. I assume it is cause the debugger actually is 'optimizing' by invoking strlen in run-time mode rather than in compiled mode. That way it can use processor and chipset features like the SCAN you mentioned above while the compiled assembly doesn't. Another idea is that the debugger actually is running a different C runtime or put it somewhere where it is not swapped.
0

Featured Post

Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

An Outlet in Cocoa is a persistent reference to a GUI control; it connects a property (a variable) to a control.  For example, it is common to create an Outlet for the text field GUI control and change the text that appears in this field via that Ou…
Examines three attack vectors, specifically, the different types of malware used in malicious attacks, web application attacks, and finally, network based attacks.  Concludes by examining the means of securing and protecting critical systems and inf…
The goal of this video is to provide viewers with basic examples to understand opening and reading files in the C programming language.
The goal of this video is to provide viewers with basic examples to understand and use conditional statements in the C programming language.

862 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question