1. Not finding help here? Sign up for a free 30min tutor trial with Chegg Tutors
    Dismiss Notice
Dismiss Notice
Join Physics Forums Today!
The friendliest, high quality science and math community on the planet! Everyone who loves science is here!

Passing pointers and arrays to a C function

  1. Jul 24, 2004 #1

    Math Is Hard

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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;
    }
     
  2. jcsd
  3. Jul 24, 2004 #2

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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
     
  4. Jul 24, 2004 #3

    Math Is Hard

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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.
     
  5. Jul 25, 2004 #4

    Math Is Hard

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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
         }
    }

     
     
  6. Jul 25, 2004 #5

    Math Is Hard

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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..?
     
  7. Jul 25, 2004 #6
    What's the point of the val variable...?
     
  8. Jul 25, 2004 #7

    Math Is Hard

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    can i test the value of found at *p with isupper()?
    I ran into some trouble with that.
     
  9. Jul 25, 2004 #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 (Text):

    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: Jul 25, 2004
  10. Jul 25, 2004 #9

    Hurkyl

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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 seperate loops).
     
  11. Jul 25, 2004 #10

    Math Is Hard

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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?
     
  12. Jul 25, 2004 #11

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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
     
  13. Jul 25, 2004 #12
    Very good point. Always check for nulls.


    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'.

    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?

    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.

    The C string manipulation functions are dangerous if not handled properly.

    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.
     
  14. Jul 25, 2004 #13

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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: Jul 25, 2004
  15. Jul 25, 2004 #14

    Math Is Hard

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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.
     
  16. Jul 25, 2004 #15

    Math Is Hard

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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.
     
  17. Jul 25, 2004 #16
    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 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.
     
  18. Jul 25, 2004 #17

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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
     
  19. Jul 25, 2004 #18
    Here is what I would do to get input from stdin:
    Code (Text):

    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 );
     
  20. Jul 25, 2004 #19

    chroot

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    e(ho0n3,

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

    - Warren
     
  21. Jul 25, 2004 #20

    Hurkyl

    User Avatar
    Staff Emeritus
    Science Advisor
    Gold Member

    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.
     
Know someone interested in this topic? Share this thread via Reddit, Google+, Twitter, or Facebook

Have something to add?



Similar Discussions: Passing pointers and arrays to a C function
Loading...