Your problem would be simplified if you used more of the string.h
functions, like strlen
. Also, you must dynamically allocate memory with malloc
and calloc
--a fixed sized buffer will not do here.
I now present the revised reverseWords
.
char *myrev(const char *line)
{
char *revword(char *);
size_t i = strlen(line);
int inword = OUT;
size_t nWord = 0, nallocWord;
char *word; // will store the word
size_t nRet = 0, nallocRet;
char *ret; // will store the entire line, but reversed
// establish preconditions
assert(i > 0);
assert(line != NULL);
// alloc memory for word and ret
if ((word = malloc(nallocWord = INITALLOC)) != NULL &&
(ret = calloc(nallocRet = INITALLOC, sizeof(char))) != NULL) {
// walk backwards through line
while (i--) {
if (inword == OUT && isalnum(line[i]))
inword = IN; // we just entered a word
if (inword == IN && isalnum(line[i])) {
// we're inside a word; append current char to the word buffer
word[nWord++] = line[i];
// word buffer exhausted; reallocate
if (nWord == nallocWord)
if ((word = realloc(word, nallocWord += ALLOCSTEP)) == NULL)
return NULL;
}
// if we are in between words or at the end of the line
if (i == 0 || inword == IN && isspace(line[i])) {
inword = OUT;
word[nWord] = '\0';
word = revword(word);
// ret buffer exhausted; reallocate
if (nRet + nWord > nallocRet)
if ((ret = realloc(ret, nallocRet += ALLOCSTEP)) == NULL)
return NULL;
// append word to ret
strcat(ret, word);
strcat(ret, " ");
nRet += nWord + 1;
nWord = 0;
}
}
free(word);
// remove trailing blank
ret[strlen(ret) - 1] = '\0';
return ret;
}
// in case of mem alloc failure
return NULL;
}
I will now explain the operation of this function.
The first line declares the function revwords
, which I will show later.
The next lines are the variable definitions. The variable i
will be used as an iterator to walk backwards. We initialize it to the length of the line
string, including the zero terminator.
The variable inword
is important. It is used to keep track of whether we are inside a word or not. It will be assigned one of two constants: IN
and OUT
.
#define IN 0 /* inside a word */
#define OUT 1 /* outside a word */
The nWord
and nallocWord
variables are respectively the number of characters in the word
buffer, and how much memory is allocated for word
. word
is where we will accumulate a word. Since the input line will be parsed backwards, the word
buffer will be initially backwards, but we will later reverse it.
The variables nRet
and nallocRet
have similar purpose: they are respectively the number of characters in the ret
buffer and the number of characters allocated for ret
. ret
is the buffer where we will store the entire input line, but with each word's position reversed.
We then enforce two preconditions: The length of the string must be positive, and the line
input buffer must not be NULL. We enforce these by using the assert
macro from <assert.h>
.
We now enter the meat of the function. Our strategy in this function will be to seize a certain amount of memory initially for our word
and ret
buffers, and then later increase the size of our buffer if need be. So we do just that.
The line
if ((word = malloc(nallocWord = INITALLOC)) != NULL &&
(ret = calloc(nallocRet = INITALLOC, sizeof(char))) != NULL) {
appears frightening at first, but if we split it into two parts, it will be easier. The part to the left of the AND operator allocates INITALLOC characters for word
, and checks if the return value is not NULL (indicating failure). But INITALLOC
is assigned to nallocWord
, which, as we said earlier, is the number of characters allocated to word
.
The part to the right of the AND allocates INITALLOC characters for ret
, and checks if the return value is not NULL. But INITALLOC
is assigned to nallocRet
. Notice that we used the calloc
function instead of malloc
. The difference lies in the fact that calloc
zero-initializes its return value, but malloc
does not. We need our ret
buffer to be zero-initialized; you will see why later.
#define INITALLOC 16 /* number of characters initially alloc'ed */
#define ALLOCSTEP 32 /* number of characters to increase by */
The values of these macros don't really matter, but you should still choose sensible values for them so that too many (slow) re-allocations aren't performed.
Anyway, inside of this if
statement, we have the while loop which iterates the string line
from the end. The while loop consists of a series of tests.
If we are outside a word (inword == OUT
) and the current character(line[i]
) is alphanumeric(i.e, a character inside a word), then we change inword
to IN
. Control will fall through to the next if
, which is
If we are inside a word (inword == IN
) and the current character is a word character, then we add the current character to the end of word
, and increase the character count nWord
. Within, we check if word
has been exhausted, in which case memory is reallocated. If the reallocation fails, we return NULL
. The reallocation works by increasing nallocWord
by ALLOCSTEP
, which is by how many characters we will resize our buffer.
If we are in between words (inword == IN && isspace(line[i]
), or if we are at the end of the line (i == 0)
, then we change inword
to OUT
, null-terminate word
, and reverse it with a call to revword
. Our next step is to add the word
to the end of ret
. However, we must first check if there is enough space for the concatenation to take place. The condition nRet + nWord > nallocRet
checks if the number of characters in ret
plus the number of characters in word
exceeds nallocRet
, which the number of characters allocated for the ret
buffer. If the condition is true, memory is reallocated. If the reallocation fails, we return NULL
. We need the check i == 0
, because when the loop is about to finish, we want to push the final word into ret
.
Now, we can append word
to ret
with a call to strcat
. We also add a space, so that the words will have spaces between each other.
nRet is updated to the new number of characters in ret
. The + 1
is to account for the space in between words. nWord is set to 0, so the next loop iteration will overwrite the old contents of word
, which are no longer needed.
Once the loop is completed, we release word
, since it is longer needed, and then remove the trailing space at the end of ret
. We then return ret
. It is the caller's responsibility, by the way, to release this memory. For every call to malloc
/calloc
, there must be a corresponding free
.
Let us now turn to revword
, which is the function to reverse a string.
char *revword(char *word)
{
char *p, *q;
assert(word != NULL);
assert(*word != '\0');
for (p = word, q = word + strlen(word) - 1; q > p; ++p, --q) {
char tmp;
tmp = *p;
*p = *q;
*q = tmp;
}
return word;
}
The function uses two character pointers, p
and q
. p
is assigned to point to the start of word
, whereas q
is assigned to point to the end of word
. The p
pointer is incremented each loop iteration, and q
is decremented, while q
is greater than p
. In the loop body, we swap the values pointed to by p
and q
.
Finally, we return the reversed word
.
Now, I will show the little bit of main
which I altered.
fgets(str, SIZE, stdin);
str[strlen(str) - 1] = '\0';
char *myrev(const char *line);
char *res = myrev(str);
printf("%s", res);
free(res);
This is inside the loop for (i = 0; i < N; i++)
.
We must remove the trailing newline from the str
buffer, which fgets
left in there. We then declare the myrev
function, and next stored the return value of myrev
in a temporary, so that we can use this pointer when we free
it.