You are on page 1of 3

Giulio" using the $ sign for padding.

Line 4 of the output (Listing 6-17 ) shows that STR_copy_string()


doesnt count the padding in its returned value, as it only counts the 6 letters of "Giulio" .
The second example in lines 8 to 10 of the code uses a NULL character for padding. This makes the
destination string look exactly as the source string when you display it in line 7 of the output because the first
NULL is interpreted as a terminator and the other four NULL s (the remaining three of padding plus the original
terminator) are ignored. Obviously, all 10 characters of the original str (plus the terminating NULL ) are still
occupied by the updated string.
The third example in lines 12 to 14 of the code attempts to overwrite the 10 characters of str with the
13 characters of " Giulio Zambon " . Line 10 of the output shows that the string is truncated. Obviously, no
padding is needed.
The fourth example in lines 16 to 18 of the code copies an empty string to str . Its purpose is to show
that, when you do so, the destination string is filled with the padding character, as shown in line 13 of the
output.
Finally, the fifth example in lines 20 to 22 of the code shows what happens when you attempt to copy a
NULL string. Perhaps not surprisingly, str_copy_string() leaves the destination string unchanged, as shown
in line 16 of the output.
The macro STR_copy_string() is only an alias for the corresponding function:
#define STR_copy_string(str, st, pad) str_copy_string(str, st, pad)
The code of str_copy_string() is shown in Listing 6-19 .
Listing 6-19. str_copy_string()
1. //------------------------------------------------------------- str_copy_string
2. size_t str_copy_string(Str *str, char *s, char pad) {
3. #if STR_LOG
4. if (pad == '\0') printf("=== str_copy_string: %p \"%s\" '\\0'\n", str, s);
5. else printf("=== str_copy_string: \"%s\" '%c' -> %p\n", s, pad, str);
6. #endif
7. if (str == NULL) {
8. STR_crash("str_copy_string: destination Str pointer NULL");
9. }
10. else if ((str)->s == NULL) {
11. STR_crash("str_copy_string: destination string pointer NULL");
12. }
13. size_t n_copy = 0;
14. if (s != NULL) {
CHAPTER 6 STRING UTILITIES
158
15. size_t len = strlen(s);
16. size_t space = str->size - sizeof(Str);
17. if (space > 0) {
18. space--;
19. n_copy = (len > space) ? space : len;
20. if (n_copy > 0) (void)memcpy(str->s, s, n_copy);
21. if (n_copy < space) (void)memset(&str->s[n_copy], pad, space - n_copy);
22. str->s[space] = '\0';
23. }
24. }
25. return n_copy;
26. } // str_copy_string
After checking that the destination is a proper string (lines 7 to 12), it calculates the length of the source
string (line 15) and the space allocated for a C string in the destination Str (line 16). Note that, if space > 0
(condition checked in line 17), it is at least 2 because str_new() doesnt allow the allocation of empty strings.
Therefore, any C string with an Str structure contains at least one character before the terminating '\0' . As
a result, after the decrementing in line 18, space is at least 1 , ensuring that at least one character is copied.
String Conversion
If you include <ctype.h> , you can use the two standard functions toupper() and tolower() to convert
individual characters.
But we want to be able to convert entire strings. To do so in a comfortable way and without duplicating
code, you can define a single function for both operations, as shown in Listing 6-20 .
Listing 6-20. str_to_upper_lower()
1. //---------------------------------------------------------- str_to_upper_lower
2. void str_to_upper_lower(Str *str, int (*f)(int)) {
3. #if STR_LOG
4. printf("=== str_to_upper_lower(%p)\n", str);
5. #endif
6. if (str != NULL && str->s != NULL) {
7. char *s = str->s;
8. while (*s != '\0') {
9. *s = (*f)(*s);
10. s++;
11. }
12. }
13. } // str_to_upper_lower
Notice that the third parameter accepts a pointer to a function that accepts an int as single parameter

Some not-so-experienced C programmers might be confused by the statement in line 9, but it will make
sense if you consider that, if f is a function pointer (remember that you passed as argument &toupper or
&tolower ), *f is the function itself. Therefore, you can imagine replacing (*f)() with either toupper() or
tolower() , like this:
*s = toupper(*s);
Then, it becomes obvious that line 9 simply picks the first character of the string and stores it back in the
same position after conversion.
The parentheses around *f are necessary because the parentheses of a function call take precedence
over the dereferencing asterisk (i.e., taking the address of the function). Therefore, if you wrote
*s = *f(*s);
you would effectively be doing the following:
*s = *(f(*s));
That is, you would be attempting to execute f (which is a pointer to a function) as if it were a function
(which it's not). One good place to see the precedence of operators is en.cppreference.com/w/c/language/
operator_precedence . It shows that the parentheses of a function call have precedence 1, while the
dereferencing asterisk (which they call indirection ) has precedence 2. Obviously, you could always use
additional parentheses to set precedences by hand if you are not sure about the default or to remove a
warning issued by an overzealous development environment, but you might like to be a minimalist in this
matter, so that when you see additional parentheses, you know that they need to be there.
The logic of the function is trivial: you go through the string and convert one character at a time until
you hit the terminating NULL .
String Clean Up
What I mean by string clean up is:
Replace all non-printing characters with spaces
Remove all leading spaces
Remove all trailing spaces
Replace all sequences of spaces with a single space
Consider, for example, the string " \t Giulio \t \x15 \r Zambon\t\t \x19\r" . After performing the
four operations listed above, it would look like this: "Giulio Zambon" . Listing 6-21 shows how you do this.
Listing 6-21. str_clean_up()
1. //---------------------------------------------------------------- str_clean_up
2. void str_clean_up(Str *str) {
3. #if STR_LOG
4. printf("=== str_clean_up: %p\n", str);
5. #endif
6. if (str != NULL && str->s != NULL) {
7. char *s0 = str->s;
8. char *s1 = s0;
CHAPTER 6 STRING UTILITIES
160
9. while (*s0 > '\0' && *s0 <= ' ') s0++; // remove leading junk
10. if (*s0 == '\0') {
11. *s1++ = ' ';
12. *s1 = '\0';
13. }
14. else {
15. int space_set = 0;
16. while (*s0 != '\0') {
17. if (*s0 > ' ') {
18. *s1++ = *s0;
19. space_set = 0;
20. }
21. else if (!space_set) {
22. *s1++ = ' ';
23. space_set = 1;
24. }
25. s0++;
26. }
27. if (s1 > str->s) { // we did something
28. *s1-- = '\0';
29. if (*s1 == ' ') *s1 = '\0'; // remove the trailing space

You might also like