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.