Passing pointers and arrays to a C function

In summary: p++; } break; case 2: while(*p) //while there is a character in the array, do something with it... *p = tolower(*p); //convert all chars to lowercase ... p++; } break; default: //do nothing, this is the default case printf("%s",val); //display the value of val }
  • #1
Math Is Hard
Staff Emeritus
Science Advisor
Gold Member
4,652
37
This is the latest assignment I am struggling with:
Write a program that contains a function void shift(char *) that looks at the first character of its argument and converts the other characters, to be the same case, that is, if the first character is uppercase, it shifts the rest to uppercase; if the first character is lowercase, it shifts the rest to lowercase, and otherwise, it does nothing.

I have written a program that will take a string and do the conversion, but I can't figure out how to wrap up the "test the first character and do the conversion" functionality into a function and make it work by only passing the pointer variable as an argument. I thought I would need to pass the whole array in order for isupper() to do the test on the first character. Or maybe this void shift(char *) syntax actually does pass the whole array - I am so clueless! :redface:
Is the void shift(char *) heading all I need to take the argument? I was thinking I would need to use some kind of temp variable void shift(char *x) in order to assign the value of the argument somewhere and then use it inside my function.

Here's what I have been able to do so far. Thanks for your advice. Sorry for the format, I am having a little trouble indenting.

# include <stdio.h>
# include <ctype.h>
int main(void)
{
char str[80], *p; // declares a string of 80 chars and a pointer called p
printf("Enter a string \n");
gets(str); //get the entered string
p = str; //pointer gets the string address

while(*p) //until a null value is found
{
if (isupper(str[0])) //determine if first char in array is uppercase
{
*p = toupper(*p); //convert them all to upper case
p++;
}
else
{
*p = tolower(*p); //otherwise make all chars lowercase
p++;
}
}
printf("%s \n",str);
return 0;
}
 
Physics news on Phys.org
  • #2
1) Use the [ code ] tags to do formatting here.

2) Arrays and pointers are the same thing in C/C++. Basically, an array is represented as a pointer to its first element.

In the following code, memory is being allocated dynamically with the new operator, and it's obvious that the new operator is returning a character pointer, which points to the first element of the array.

char* myArray = new char[10];

In the following code, memory is being allocated statically. While it may not be obvious that myArray is in fact a char*, it is.

char myArray[10];

You've already used this fact in your statement that says "p = str;"

3) When you pass a char*, you're either passing a pointer to a single character, or passing a pointer to the first element of an array of characters. In fact, it's the same thing: a char* points to one (or more) characters. To get to the next character in the array, add 1 to the pointer, or use the bracket notation (they are equivalent notation and in fact result in the same machine instructions!):

char test = *(myArray + 1);
char test = myArray[1];

4) If I were you, I'd do the test to determine what to do (toupper or tolower) before entering the while loop to do the conversion. What you have works, but it's a little hard to read.

- Warren
 
  • #3
Thanks for all that advice. I am going to go back and work on this some more.

"Arrays and pointers are the same thing in C/C++. Basically, an array is represented as a pointer to its first element. "

I think this has been the hardest thing for me to learn.
 
  • #4
better?

PHP:
  # include <stdio.h>
# include <ctype.h>
void shift(char*); //prototype
int main(void)
{
     char str[80], *pointer; // declares string of 80 chars & pointer called p
     printf("Enter a string \n");
     gets(str); //get the entered string
     pointer = str; //pointer gets the string address

     shift(pointer); //call the function
     printf("%s \n",str); // print string after function has run
}

void shift(char*p)
{
     char val = *p; //assigns value found at the address to val variable
     int result = 0; //holds result of upper or lower lest below

     if (isupper(val)) //test for upper case
          result = 1;
     else if (islower(val)) //test for lower case
          result = 2;

     switch(result) //converts case based on upper or lower test
     {
     case 1:
          while(*p)
          {
               *p = toupper(*p); //convert them all to upper case
               p++;	
          }
          break;
     case 2:
          while(*p)
          {
               *p = tolower(*p); //convert them all to upper case
               p++;	
          }
          break;
     default:
          break; // does nothing
     }
}
 
  • #5
I used if/else to test the first char, and then a switch statement to do the work. Wasn't sure if this is still too long and unwieldy though..?
 
  • #6
What's the point of the val variable...?
 
  • #7
can i test the value of found at *p with isupper()?
I ran into some trouble with that.
 
  • #8
Using *p as the conditional in your while loop is VERY dangerous. In your shift function, I would recommend passing the size of the array, i.e. shift(char* p, unsigned int size). This way you can make your while loop safer. You don't need the two while loops. You can just check whether to use toupper() or tolower() inside one while loop, e.g.
Code:
i = 0;
while (i < size) {
  if (result == 1) p[i] = toupper(p[i]);
  else if (result == 2) p[i] = tolower(p[i]);
  i++;
}

Hope that helps.
 
Last edited:
  • #9
I disagree entirely.

Using *p as the conditional for a loop is the standard paradigm for looping through null terminated strings. However, *p != 0 is sometimes suggested as being more readable, and I would advise using for(; *p != 0; ++p) loop instead of a while loop, again for readability.

The case where p is null is easily handled (as an aside, neither of you test if p is null. Shame on you!), so I presume your suggestion is to avoid problems in the case where the argument is not a null-terminated string. I think this is bad, for several reasons:

(a) You are giving the programmer rope to hang himself. If the string is not null-terminated, this is a logic error which needs to be identified and fixed, not covered up.

(b) It is inconvenient and inefficient; the length of the string is unnecessary for the operation of shift, and it will usually require unnecessary time/space to have it available.

(c) Typically, you fix nothing; the programmer will just crash in strlen while getting the length instead of crashing in shift.

(d) It breaks with C style where string manipulation functions don't require length arguments. (Though they often have variants that operated on a fixed size character array, such as strncpy)


As for putting your test inside the while loop, that is entirely a matter of taste, and hopefully the compiler will compile both to the same code (which is probably two separate loops).
 
  • #10
Thanks for your suggestions, e(ho0n3 and Hurkyl.

The case where p is null is easily handled

regarding that "p is null" test - I am not sure I understand.. my while loops check for *p to have a value. I think maybe you're telling that if my function were used by another program, it might encounter a string with no null terminator?
Do I need to insert one at the end of the string before it goes through the loop?
 
  • #11
Sorry Hurkyl, but I hope you don't write life-support code! You are in fact quite wrong.

A well-written program would not use gets() in the first place. It would use fgets() and avoid the possibility of buffer overrun. fgets() guarantees you won't overrun your buffer, and guarantees that the null byte is within the bounds of the array. Even though you now have this guarantee, I would still suggest passing the length of the string to the conversion function anyway.

What's humorous to me is that you made a big stink about the efficiency of counting the number of characters in the string, and then went on to suggest it's okay to check the first character's case in each iteration of the while loop. (They certainly won't compile to the same code, that's for sure.) How's that for efficiency?

- Warren
 
  • #12
Hurkyl said:
The case where p is null is easily handled (as an aside, neither of you test if p is null. Shame on you!), so I presume your suggestion is to avoid problems in the case where the argument is not a null-terminated string.
Very good point. Always check for nulls.


(a) You are giving the programmer rope to hang himself. If the string is not null-terminated, this is a logic error which needs to be identified and fixed, not covered up.
Never assume that some char* is null-terminated. The best thing to do is to check it. For example: allow only input of at most 20 characters and check that one of these 20 characters containls '\0'.

(b) It is inconvenient and inefficient; the length of the string is unnecessary for the operation of shift, and it will usually require unnecessary time/space to have it available.
The time/space necessary to pass another parameter to the function is neglible. Security is a more important issue. You don't want your program to have bugs do you?

(c) Typically, you fix nothing; the programmer will just crash in strlen while getting the length instead of crashing in shift.
The use of strlen is not recommend for safe programming. It's funny how this little function has caused so many of the buffer overflow problems that hackers/crackers use for malignant purposes.

(d) It breaks with C style where string manipulation functions don't require length arguments. (Though they often have variants that operated on a fixed size character array, such as strncpy)
The C string manipulation functions are dangerous if not handled properly.

As for putting your test inside the while loop, that is entirely a matter of taste, and hopefully the compiler will compile both to the same code (which is probably two separate loops).
You can't rely on what you "hope" the compiler will do to your code if you want an efficient implementation. I'm sure that if I produce the assembly code for both versions of the loop, my version will contain only one loop. However, my version is less efficient because the condition inside the while loop will be executed in each iteration, effectively slowing things down. Using the two loops is more efficient.
 
  • #13
Math Is Hard said:
Thanks for your suggestions, e(ho0n3 and Hurkyl.

The case where p is null is easily handled

regarding that "p is null" test - I am not sure I understand.. my while loops check for *p to have a value. I think maybe you're telling that if my function were used by another program, it might encounter a string with no null terminator?
Do I need to insert one at the end of the string before it goes through the loop?
We're getting into software engineering territory here. Basically, there are two schools of thought on the issue:

1) Some people say it's good idea to write every function to operate properly for every possible input. The input to your function is a char*, so the domain is every possible array of characters. The person who calls your function might make a mistake and give you an array that contains no null character in it. They might be malicious and manipulate some memory to cause this to happen, and therefore to cause your program to crash. If you write your function to gracefully handle every single possible input, even the ones that aren't supposed to happen, your function will never crash. Pros: it's very portable; you can toss it into any program and it'll never crash, even if you make a mistake elsewhere in your program. It's nice to not have to chase bugs back into code you wrote years ago. It's also not a security risk, since it can gracefully handle any input you can dream up. Cons: it's typically a little slower, since it has to do more sanity checks. You also have to do something when you detect an error. For some functions, that might be nothing more than returning -1 or some other error code. For more complicated software components, it might require actually interacting with the user, with all its attendant cross-platform portability issues, etc.

2) Other people say it's a good idea to write code "by contract." In other words, your function is designed to handle a null-terminated C string. In the documentation, you should specify specifically that this function must be provided a null-terminated C string to work. Any programmer who violates this contract is asking for trouble, and it's his/her fault, not yours. Pros: it might be easier to write. It will execute a little faster. Cons: it requires you and other programmers to be very careful with what they pass your function. It requires them to constantly go back and read your documentation and make sure they are meeting the function's contract. You might find that a malicious user doesn't care too much about meeting the function's contract at all.

What school of thought am I in? Number one. I'm a huge proponent of zero-defect software verification techniques, and I'm a huge proponent of write-once-and-forget philosophy. I don't want to write a function that has a list of caveats. I want to write an invulnerable fortress of a function that I can trust to always be sensible. I don't want to ever have to look at its code again. I want to be able to drop it into any system I write in the future, anywhere, and have it simply work.

In this case of this homework assignment, it's more of less a moot point -- but you might want to start thinking about the big picture, too. :smile:

- Warren
 
Last edited:
  • #14
e(ho0n3 said:
Never assume that some char* is null-terminated. The best thing to do is to check it. For example: allow only input of at most 20 characters and check that one of these 20 characters containls '\0'.

where should I do this? Right after I get the string from input? Should I just loop through the string characters and check for the '\0'?

thanks.
 
  • #15
chroot said:
In this case of this homework assignment, it's more of less a moot point -- but you might want to start thinking about the big picture, too. :smile:

- Warren

Thanks so much. I agree with what you said. I admire the programmers I work with because they write their code so carefully that they are all able to swap pieces with each other to add functionality to their programs, with very little effort other than copying and pasting a function sometimes! I think it is pretty amazing that they have worked out the use cases so thouroughly that this is possible.
 
  • #16
Math Is Hard said:
regarding that "p is null" test - I am not sure I understand.. my while loops check for *p to have a value.
All characters have a value including NULL which has a value of 0. Note that 'while (*p)' will stop if *p is NULL (i.e. 0 since 'while (0)' will stop the loop).

I think maybe you're telling that if my function were used by another program, it might encounter a string with no null terminator?
Do I need to insert one at the end of the string before it goes through the loop?
I guess it maybe a little early to introduce the concept of safe coding. Nonetheless, you can't assume that someone will always use your program properly (e.g. input null-terminated strings). If the string doesn't have a null terminator the while loop will cause the program to crash or do some other malicious deed.
 
  • #17
You can use the safe function strnlen (notice the N, it's strnlen, not strlen) to count the length of your string for you. You must pass it the length of the array, and it will not return a value past the end of the array, even if the array does not contain a NULL.

- Warren
 
  • #18
Math Is Hard said:
where should I do this? Right after I get the string from input? Should I just loop through the string characters and check for the '\0'?
Here is what I would do to get input from stdin:
Code:
char buff[50] = ""; // Creates an array of 50 '\0', effectively null terminated

// Get 20 characters of input from stdin. Note that we are setting a limit
// less than the size of buff (which is 50). This guarantees that buff will
// always be null-terminated.
fgets( buff, 20, stdin );
 
  • #19
e(ho0n3,

That's not necessary, and a waste of memory. fgets() guarantees its output to contain a null.

- Warren
 
  • #20
regarding that "p is null" test - I am not sure I understand.. my while loops check for *p to have a value.

I mean that you should check that p itself is not null; that is, the very first thing you do is test if p is a null pointer p == 0. Note that "null" in the context of a pointer is different than null in the context of a character.

If p is indeed a null pointer, you would want to indicate the error somehow; the least complex way is to simply write a message to the screen.
 
  • #21
Hurkyl said:
I mean that you should check that p itself is not null; that is, the very first thing you do is test if p is a null pointer p == 0. Note that "null" in the context of a pointer is different than null in the context of a character.

If p is indeed a null pointer, you would want to indicate the error somehow; the least complex way is to simply write a message to the screen.

If p is a null pointer, does that mean my pointer variable p never got an address to store, even though I assigned it one?

thanks.
 
  • #22
Yes, a "null pointer" is a pointer that points to address 0x000000. Normally this address points you into some BIOS code, and is rarely what you what to be reading. Null pointers are commonplace, usually as error signals. The new operator, for example, will return a null pointer when it fails to allocate enough memory.

In this particular program, there is no way p can be null, because you are statically allocating your character array. However, as we've said, it's a good idea for a function to do sanity checks on all its arguments, and one such sanity check is making sure pointers are not null.

- Warren
 
  • #23
Thanks. That makes sense. My teacher does mention something about this for the next exercise (which I haven't started yet) so I am sure I'll be dealing with it soon enough.

here's the comment he added to the upcoming exercise:
Note that if the function does not find the character, it should return a NULL pointer, not a pointer to a NULL character.
 
  • #24
What school of thought am I in? Number one. I'm a huge proponent of zero-defect software verification techniques, and I'm a huge proponent of write-once-and-forget philosophy. I don't want to write a function that has a list of caveats. I want to write an invulnerable fortress of a function that I can trust to always be sensible. I don't want to ever have to look at its code again. I want to be able to drop it into any system I write in the future, anywhere, and have it simply work.

The biggest problem I have is that you did not achieve this with your modification. Frankly, I prefer a function that crashes on bad input than one that doesn't crash, but produces wrong behavior. And besides, you've done nothing to stop a malicious user, because he just has to put INT_MAX for the length. In fact, as currently written, the "safe" version is more dangerous because it allows a malicious user to write beyond valid strings! (though this deficiency is easy enough to fix)


One basic problem here is that it's not possible for shift to do the necessary error checking. Another is that it seems natural to cast shift as a low level function, well insulated by the surrounding code. Unless you're providing a library of low level routines, this is not a function that should be exposed to the untrusted. If your code doesn't already have some way to guarantee strings are valid, then there are problems far more serious than anything that could go wrong in shift.



What's humorous to me is that you made a big stink about the efficiency of counting the number of characters in the string, and then went on to suggest it's okay to check the first character's case in each iteration of the while loop. (They certainly won't compile to the same code, that's for sure.) How's that for efficiency?

I thought (incorrectly) that compilers were capable of this sort of rewriting. Incidentally, it turns out that putting the result == 1 generates faster code on my system when I compiled my test program with g++ -O3. (Though the two loop version was faster under other parameters)
 
  • #25
Hurkyl,

If the first character is tested in each iteration of the loop, the result cannot be cached. You're looking at a piece of non-register memory that may have been modified by another thread, or even as a side-effect of one of the functions in the loop. Also, you're making a function call to test the character, and no compiler is going to cache the result of a function call, unless it is inlined.

- Warren
 
  • #26
I've not been suggesting you put the function call in the loop. I was referring to e(hoOn3's piece of code:

Code:
while (i < size) {
  if (result == 1) p[i] = toupper(p[i]);
  else if (result == 2) p[i] = tolower(p[i]);
  i++;
}

(though I only used one if and for(; *p; ++p) for my loop)
 
  • #27
Aha, sorry Hurkyl, I must have misread something.

- Warren
 
  • #28
chroot said:
That's not necessary, and a waste of memory. fgets() guarantees its output to contain a null.
Correct. I tend to be overly protective of the code I write since I tend to forget the details of what each function does (especially since I haven't been using the 'standard' libraries for a while now). Being overprepared is better than not at all.
 
  • #29
I have my own code for reading lines from files too. :biggrin:
 
  • #30
Hurkyl,

I've written my own filesystems, too! I've written file I/O code several times just because I wanted some of the handy features of stream I/O, but didn't want the huge overhead of the full C++ stream implementations.

- Warren
 
  • #31
chroot said:
You can use the safe function strnlen (notice the N, it's strnlen, not strlen) to count the length of your string for you. You must pass it the length of the array, and it will not return a value past the end of the array, even if the array does not contain a NULL.

Interestingly enough, I couldn't find strnlen() mentioned in either of my books. How many arguments does it take? Thank you.
 
  • #33
Cheers!
I hope the professor won't mind me using something that's not in the text. I'll give it a try!
 
  • #34
I have a question about incrementing, if anyone has a moment. I as playing with a function example in an old book I have. It copies one string to another when you give it the arguments of pointers to the string to be copied to and the string to be copied from.

void StringCopy(char *to, char*from)
{
while(*from)
*to++ = *from++;
*to = '\0';
}

And it made me wonder ... it appears to me that moving the characters from "from" to "to" doesn't start until the pointer is at the second element, because it starts at to++ which is "to + 1" instead of starting at "to + 0". Yet it copies the first element over.
Also, it appears that the function is filling the string with null values after each completed copy to the new string. Why is this being done?
Thanks.
 
  • #35
Math Is Hard said:
And it made me wonder ... it appears to me that moving the characters from "from" to "to" doesn't start until the pointer is at the second element, because it starts at to++ which is "to + 1" instead of starting at "to + 0". Yet it copies the first element over.
Also, it appears that the function is filling the string with null values after each completed copy to the new string. Why is this being done?
Thanks.
I rewrote the code with some formatting.
Code:
void StringCopy(char *to, char*from)
{
  while(*from)
    *to++ = *from++;
  *to = '\0';
}
As you can see, the last line in the function is executed only after the loop terminates. Also note that

*to++ = *from++;

translates to

*to = *from;
to++; from++;

Hope that helps.
 

Similar threads

  • Engineering and Comp Sci Homework Help
Replies
2
Views
946
  • Programming and Computer Science
Replies
5
Views
884
  • Engineering and Comp Sci Homework Help
Replies
3
Views
754
  • Engineering and Comp Sci Homework Help
Replies
17
Views
1K
  • Programming and Computer Science
Replies
23
Views
1K
  • Engineering and Comp Sci Homework Help
Replies
12
Views
2K
  • Engineering and Comp Sci Homework Help
Replies
8
Views
5K
  • Programming and Computer Science
Replies
2
Views
933
  • Engineering and Comp Sci Homework Help
Replies
11
Views
2K
  • Programming and Computer Science
Replies
6
Views
2K
Back
Top