Related
I am trying to write a program that will dynamically allocate enough space to store all the words in a 1D char array separated by a space.
ex:
char *literal = "The quick brown fox";
char **words = { "The", "quick", "brown", "fox" };
The program I wrote keeps segfaulting when trying to strncpy(str[buff_ptr],tok,strlen(tok));
I will post my code bellow:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *mutableString(char *lit) {
int size = strlen(lit);
char *str = (char *)malloc(sizeof(char) * size);
strncpy(str, lit, size + 1);
return str;
}
int numTokens(char *str, const char *DELIM) {
char* clone = (char*)malloc(sizeof(char*));
strncpy(clone, str, strlen(str) + 1);
int count = 0;
for (char *tok = strtok(clone, " "); tok != NULL; tok = strtok(NULL, " "))
count++;
free(clone);
return count;
}
char **tokenize(char *str, const char *DELIM) {
printf("tokenize-------------------------\n");
int size = numTokens(str, DELIM);
//allocate space on heap for buffer
char **buff = (char **)malloc(size * sizeof(char *));
//get first word
char *tok = strtok(str, DELIM);
int buff_ptr = 0;
while (tok != NULL) {
strncpy(buff[buff_ptr], tok, strlen(tok) + 1);
printf("buff[%d]%s\n", buff_ptr, buff[buff_ptr]);
//increment to next word for storage
buff_ptr++;
//find next word in string
tok = strtok(NULL, DELIM);
}
for (int i = 0; i < size; i++) {
printf("%s\n", buff[i]);
}
//return 2D pointer
return buff;
}
int main() {
char *literal = "some literal string.";
//convert string to mutable string for strtok
char *str = mutableString(literal);
//set 2D pointer equal to the pointer address returned
char **no_spaces_str = tokenize(str, " ");
printf("%s\n", str);
for (int i = 0; i < numTokens(str, " "); i++) {
printf("%s\n", no_spaces_str[i]);
}
//free heap allocated memory
free(str);
free(no_spaces_str);
return 0;
}
Please see attachment of lldb stack variables:
Within the function mutableString there is dynamically allocated the character array str that does not contain a string
char* mutableString(char* lit){
int size = strlen(lit);
char* str = (char*)malloc(sizeof(char)*size);
strncpy(str,lit,size);
return str;
}
So other functions invoke undefined behavior as for example in this for loop
int numTokens(char* str, const char* DELIM){
int count = 0;
for(; *str != '\0'; str++)
//...
Moreover if the array contained a string nevertheless the function numTokens is incorrect because for example it returns 0 when a passed string contains only one word.
Also in the function tokenize
strncpy(buff[buff_ptr],tok,strlen(tok));
there are used uninitialized pointers buff[buff_ptr] allocated like.
char **buff = (char**)malloc(size*sizeof(char*));
And again you are trying to copy strings without including the terminating zero character '\0; using eth functions strncpy.
So this call in main
printf("%s\n",no_spaces_str[i]);
also will invoke undefined behavior.
This is the corrected version of the code above
When you copying string you should add 1 char for '\0'
int size = strlen(lit)+1;
Tokens buffer size should be size+1
int size = numTokens(str, DELIM)+1;
Strncpy is not required strncpy(buff[buff_ptr], tok, strlen(tok) + 1);
you already copied string char* str = mutableString(literal);
just point to n-th buffer every next token buff[buff_ptr]=tok;
for (int i = 0; i<numTokens(str, " "); i++){
printf("%s\n", no_spaces_str[i]);
}
This code wouldn't work correctly. strtok manipulates the string you pass in and returns a pointer to it, so no memory is allocated.
so all spaces will be replaced by '\0'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#pragma warning(push)
#pragma warning(disable : 4996)
char* mutableString(char* lit){
int size = strlen(lit)+1;
char* str = (char*)malloc(sizeof(char)*size);
strncpy(str, lit, size);
return str;
}
int numTokens(char* str, const char* DELIM){
int count = 0;
for (; *str != '\0'; str++)
{
if (*str == ' ')
count++;
}
return count;
}
char** tokenize(char* str, const char* DELIM){
printf("tokenize-------------------------\n");
int size = numTokens(str, DELIM)+1;
//allocate space on heap for buffer
char **buff = (char**)malloc((size)*sizeof(char*));
//get first word
char* tok = strtok(str, DELIM);
int buff_ptr = 0;
while (tok != NULL){
buff[buff_ptr]=tok;
printf("buff[%d]%s\n", buff_ptr, buff[buff_ptr]);
//increment to next word for storage
buff_ptr++;
//find next word in string
tok = strtok(NULL, DELIM);
}
for (int i = 0; i<size; i++){
printf("%s\n", buff[i]);
}
//return 2D pointer
return buff;
}
int main(){
char* literal = "some literal string.";
//convert string to mutatable string for strtok
char* str = mutableString(literal);
//set 2D pointer equal to the pointer addres returned
char** no_spaces_str = tokenize(str, " ");
printf("%s\n", str);
for (int i = 0; i<numTokens(str, " "); i++){
printf("%s\n", no_spaces_str[i]);
}
//free heap allocated memory
free(str);
free(no_spaces_str);
return 0;
}
results
tokenize-------------------------
buff[0]some
buff[1]literal
buff[2]string.
some
literal
string.
some
char* mutableString(char* lit){
int size = strlen(lit)+1;
char* str = (char*)malloc(sizeof(char)*size);
strncpy(str,lit,size);
return str;
}
int numTokens(char* str, const char* DELIM){
int size = strlen(str)+1;
char* clone = (char*)malloc(sizeof(char)*size);
strncpy(clone,str,size);
int count = 0;
for(char* tok = strtok(clone," "); tok != NULL; tok=strtok(NULL, " "))
count++;
free(clone);
return count;
}
char** tokenize(char* str, const char* DELIM){
int size = strlen(str)+1;
char* clone = (char*)malloc(sizeof(char)*size);
strncpy(clone,str,size);
// printf("tokenize-------------------------\n");
int size = numTokens(str, DELIM);
//allocate space on heap for buffer
char **buff = (char**)calloc(size,sizeof(char*));
//get first word
char* tok = strtok(clone,DELIM);
int buff_ptr = 0;
while(tok != NULL){
// printf("token%d:%s\n",buff_ptr,tok);
buff[buff_ptr] = (char*)malloc(sizeof(char)*strlen(tok)+1);
strncpy(buff[buff_ptr],tok,strlen(tok)+1);
//increment to next word for storage
buff_ptr++;
//find next word in string
tok = strtok(NULL, DELIM);
}
//return 2D pointer
free(clone);
return buff;
}
int main(){
char* literal = "some literal string.";
//convert string to mutatable string for strtok
char* str = mutableString(literal);
//set 2D pointer equal to the pointer addres returned
char** no_spaces_str = tokenize(str, " ");
int num_words = numTokens(str," ");
char* oneD = (char*)calloc(strlen(str)+1,sizeof(char));
for(int i = 0;i<num_words;i++){
strncat(oneD,no_spaces_str[i],strlen(no_spaces_str[i])+1);
printf("%s\n",oneD);
}
//free heap allocated memory
free(str);
free(no_spaces_str);
free(oneD);
return 0;
}
Is solution to my problem. Thanks to all those who commented and helped me understand dynamic memory better.
Apart from the #Vlad from Moscow mentioned points,
malloc return values must not be type-casted Do I cast the result of malloc?
I tried to clean up the code find the snippet below, DEMO
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
char** buff;
int size;
}Array_2d;
char* mutableString(const char* lit){
int size = strlen(lit);
char* str = malloc(size);
strncpy(str,lit,size+1);
return str;
}
int getNextWordLength(const char* str){
int index = 0;
while(*str && (*str != ' ')){
//printf("%c",*str);
++index;
++str;
}
return index;
}
int numTokens(const char* str){
int count = 0;
for(; *str != '\0'; str++)
{
if(*str == ' ')
count++;
}
return count;
}
void tokenize(const char* str, const char *DELIM, Array_2d *array){
int len = strlen(str)+1;
if(!str && !len){
array->buff = 0;
array->size = 0;
}
int number_of_words = numTokens(str)+1;
//allocate space on heap for buffer
char **buff = (char**)malloc(number_of_words*sizeof(char*));
int index = 0;
do{
//get first word
int word_length = getNextWordLength(str);
//To compensate null terminal
buff[index] = malloc(word_length+1);
strncpy(buff[index], str,word_length);
buff[index][word_length+1] = '\0';
str += word_length+1;
++index;
}while(index < number_of_words);
//update return value
array->buff = buff;
array->size = number_of_words;
}
int main(){
char* literal = "hello world this is test";
//convert string to mutatable string for strtok
char* str = mutableString(literal);
printf("Complete String is : %s\n",str);
Array_2d array;
// set 2D pointer equal to the pointer addres returned
tokenize(str, " ",&array);
printf("Tokenized String\n");
for(int i=0;i<array.size;i++){
printf("%s\n",array.buff[i]);
}
free(str);
for(int i =0;i< array.size; ++i)
free(array.buff[i]);
free(array.buff);
return 0;
}
I want to create a c program that when the user enters some words like this: "some,words, in, c, proramming." the program save words in the string "str", then it creates Dynamically a 2D array and copies the words into the 2D array:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#include <conio.h>
void freeMememory(int**array, int row){
for(int i=0;i<row;i++)
free(array[i]);
free(array);
}
int lettersCount(char *arr){
int space=0, letters=0;
do{
if(*arr !=' '&& *arr!='\t' && *arr!=','&& *arr!='.'){
letters =letters+1;
}
++arr;
}while(*arr);
return letters;
}
int wordCount(char *arr){
int space=0, words=0;
for(int i=0; arr[i]!='\0'; i++){
if(arr[i] ==' '|| arr[i]=='\t'|| arr[i]=='\n'||arr[i]==','||arr[i]=='.'){
space++;
}
if(space>0){
words++;
space=0;
}
}
return words;
}
int main (){
char arr[100];
int i, j, row, column;
scanf("%[^\n]s", &arr);
int *words = wordCount(arr);
int *letters = lettersCount(arr);
row=words;
column=letters;
int **ptr = (int **)malloc(row*column*sizeof(int));
for(i=0;i<row;i++){ptr[i]=(int*)malloc(column*sizeof(int));}
/*
//how should I write here to copy only words from arr to ptr?
like this:
arr = "some words, two,three,four."
ptr = {
"some", "words", "two", "", "three", "four",
}
*/
freeMememory(ptr, row);
return 0;}
So any ideas how to copy only the words from the string into the 2D array without copying (periods, spaces, cammas)?
What you might be looking for is strtok from <string.h>. I will also replace row with rows and column with columns in the following code snippet, as suggested by tadman in the comments.
/* no need to cast `malloc` */
char *ptr[rows];
for (int i = 0; i < rows; ++i) {
ptr[i] = malloc(columns);
if (!token) {
fprintf(stderr, "Error: memory allocation failed\n");
exit(EXIT_FAILURE);
}
}
const char *delims = " \t\n,.";
/* second argument are delimiters */
strcpy(ptr[0], strtok(arr, delims));
for (int i = 1; i < rows; ++i)
strcpy(ptr[i], strtok(NULL, delims));
I would also suggest simplifying your functions. For example your wordCount function could probably be simplified to this:
int count_words(char *str, const char *delims)
{
words = 1;
for (int i = 0; str[i] != '\0'; ++i)
if (strchr(delims, str[i]))
++words;
return words;
}
The function count_words could then be called like this:
const char *delims = " \t\n,.";
int words = count_words(arr, delims);
First notice that your code isn't using a 2D array. It's using an array of char-pointers that each point to a char-array. It's a different thing but it can be used in much the same way.
Below is an implementation that uses strtok to split the input string. Further, it uses realloc to make the array of char-pointers grow when a new word is found. Finally it uses a sentinel (i.e. NULL) to indicate end-of-words.
The code is pretty simple but the performance is poor.
Example:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
char** split(const char* str)
{
if (str == NULL) exit(1);
// Copy input string as strtok changes its input
char* str_cpy = malloc(strlen(str) + 1);
if (str_cpy == NULL) exit(1);
strcpy(str_cpy, str);
unsigned num_rows = 0;
char** arr = NULL;
// Get first token
const char *delims = " \t\n,.";
char* ptr = strtok(str_cpy, delims);
while (ptr)
{
// Allocate one more row
arr = realloc(arr, (num_rows + 1) * sizeof *arr);
if (arr == NULL) exit(1);
// Allocate memory for one more word
arr[num_rows] = malloc(strlen(ptr) + 1);
if (arr[num_rows] == NULL) exit(1);
strcpy(arr[num_rows], ptr);
++num_rows;
// Get next token
ptr = strtok(NULL, delims);
}
// Add a sentinel to indicate end-of-words
arr = realloc(arr, (num_rows + 1) * sizeof *arr);
if (arr == NULL) exit(1);
arr[num_rows] = NULL;
free(str_cpy);
return arr;
}
int main(void)
{
char* str = "some,words, in, c, programming.";
char** arr = split(str);
printf("Original string: %s\n", str);
for (int i=0; arr[i] != NULL; ++i)
{
printf("Word[%d]: %s\n", i, arr[i]);
}
// Free array
for (int i=0; arr[i] != NULL; ++i)
{
free(arr[i]);
}
free(arr);
return 0;
}
Output:
Original string: some,words, in, c, programming.
Word[0]: some
Word[1]: words
Word[2]: in
Word[3]: c
Word[4]: programming
I'm building a word counter program. To achieve this, I was thinking about saving the string the user inputted, and using strtok() to split the sentence with space as the delimiter. But first I want to allocate enough memory for each word. Let's say the sentence is "Hello World". I've already dynamically allocated memory for the string itself. Now I want to split Hello World into 2 strings, "Hello" and "World". My goal is to allocate enough memory so that there's not too much empty space but I also don't want to allocate too little space. Here is my code so far:
#include <stdio.h>
#include <stdlib.h>
char *strmalloc(char **string);
char *user_input = NULL;
char *word_array[];
int main(void) {
printf("Enter a sentence to find out the number of words: ");
user_input = strmalloc(&user_input);
return 0;
}
char *strmalloc(char **string) {
char *tmp = NULL;
size_t size = 0, index = 0;
int ch;
while ((ch = getchar()) != '\n' && ch != EOF) {
if (size <= index) {
size += 1;
tmp = realloc(*string, size);
if (!tmp) {
free(*string);
string = NULL;
break;
}
*string = tmp;
}
(*string)[index++] = ch;
}
return *string;
}
How would I go about doing this? Should I do the splitting first or allocate the space required for the array first?
You can count words without splitting the sentence, here is an example :
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
// Change this to change the separator characters
static inline char isSeparator(char ch) { return isspace(ch) || ispunct(ch); }
char * jumpSeparator(char *string) {
while(string[0] && isSeparator(string[0])) string++;
return string;
}
char * findEndOfWord(char *string) {
while (string[0] && !isSeparator(string[0])) string++;
return string;
}
int countWords(char *string) {
char * ptr = jumpSeparator(string);
if (strlen(ptr) == 0) return 0;
int count = 1;
while((ptr = findEndOfWord(ptr)) && ptr[0]) {
ptr = jumpSeparator(ptr);
if (!ptr) break;
count++;
}
return count;
}
int main() {
char * sentence = "This is,a function... to||count words";
int count = countWords(sentence);
printf("%d\n", count); //====> 7
}
EDIT : Reusing the same functions here is another example that allocates substrings dynamically :
int main() {
char * sentence = "This is,a function... to||split words";
int count = countWords(sentence);
char * ptr = sentence, *start, *end;
char ** substrings = malloc(count * sizeof(char *));
int i=0;
while((ptr = jumpSeparator(ptr)) && ptr[0]) {
start = ptr;
ptr = findEndOfWord(ptr);
end = ptr;
int len = end-start;
char * newString = malloc(len + 1);
memcpy(newString, start, len);
newString[len] = 0;
substrings[i++] = newString;
}
// Prints the result
for(int i=0; i<count; i++) printf("%s\n", substrings[i]);
// Frees the allocated memory
for(int i=0; i<count; i++) free(substrings[i]);
free(substrings);
return 0;
}
Output :
This
is
a
function
to
split
words
I'm trying to create a split function using strtok and a dynamic array.
However, I have no clue where things are going wrong: No informative error messages.
It does say segmentation fault, but I don't understand how the heap is corrupt or whatever causes that happens.
Would someone be willing to explain to me what is wrong and how to do it correctly?
Edit 11:16 CST code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char **toArray(char **array, char str[], char sep[], int *count);
char** my_split(const char* str, char delim, int* size);
int main(int argc, char* argv[]) {
char* test = "Hello there lol";
int *count = 0;
char **array = malloc(sizeof(char*) * 5);
toArray(array, test, " ", count);
printf("Count: %d\n", *count);
int array_i;
for (array_i = 0; array_i < *count; array_i++) {
printf("array %d: %s\n", array_i, array[array_i]);
free(array[array_i]);
}
free(array);
return 1;
}
char **toArray(char **array, char str[], char sep[], int *count) {
char *temp = str;
temp = strtok(temp, sep);
array[0] = temp;
*count = 1;
while ((temp = strtok(NULL, sep)) != NULL ) {
array[(*count)++] = temp;
}
return array;
}
Compiler messages are our friend. I simpley used them to track down your issues. Try the following, and compare whats been done to what you had. Special attention to decalration and usage of pointer variables... :)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char **toArray(char **array, char str[], char sep[], int *count);
int main(int argc, char* argv[]) {
char test[] = "Hello there lol";
int count = 0;
char **array = malloc((sizeof(char*) * 5) +1); //added "+ 1" here, read why
toArray(array, test, " ", &count); //in comment below
printf("Count: %d\n", count);
int array_i;
for (array_i = 0; array_i < count; array_i++) {
printf("array %d: %s\n", array_i, array[array_i]);
//free(array[array_i]);
}
getchar();
free(array);
return 1;
}
char **toArray(char **array, char str[], char sep[], int *count) {
char *temp = str;
temp = strtok(temp, sep);
array[0] = temp;
*count = 1;
while ((temp = strtok(NULL, sep)) != NULL) {
array[(*count)++] = temp;
}
return array;
}
[EDIT] Example Output:
Also. The line char **array = malloc(sizeof(char*) * 5);, needed to be
char **array = malloc(sizeof(char*) * 5 + 1); because "hello" is actually 5 chars plus a NULL char, '\0'.
Some rules of thumb for C string(s).
1) when using malloc or calloc, don't forget to allow room for '\0'.
`char *buf1;` //buffer needed to manipulate buf2
`char buf2[]="someString";`
`buf1 = malloc(strlen(buf2)+1);` or `buf1 = malloc(sizeof(buf2));`
(note:, no '+1'. see '4)' below. )
2) clear (initialize) new allocated variable before use. eg:
memset(buf, 0, strlen("someString")+1); //preferred, all bytes are zeroed
OR
buf[0]=0; //useful, but use with care (only first byte is zeroed.)
3) Free all dynamically allocated memory when done with it. Eg:
free(buf);
4) Using strlen() function or sizeof() macro. (both popular for use in [mc]alloc())
Given:
char *buf1 ="Hello"; //6 characters |H|e|l|l|o|\0|
char buf2[] ="Hello"; //6 characters |H|e|l|l|o|\0|
char buf3[5]="Hello"; //5 characters |H|e|l|l|o|
char buf4[5]="Hel"; //4 characters |H|e|l|\0| |
char buf5[5]="Helloo";//should get compile error, too many initializers
Compare strlen() - sizeof() results:
strlen(buf1); //->5 (requires +1 in malloc for new variable req'd to hold "Hello\0")
sizeof(buf1); //->4 (returns sizof (char *), not # chars in string)
strlen(buf2); //->5 (requires +1 in malloc for new variable req'd yo hold "Hello\0")
sizeof(buf2); //->6 (counts all chars, including '\0')
strlen(buf3); //-> (error: Missing terminating NULL in string argument)
sizeof(buf3); //->5 (counts all chars, but there is no '\0' in this string - wrong!)
strlen(buf4); //->3 (counts chars, but not '\0')
sizeof(buf4); //->5 (counts ALL allocated space, including '\0')
You are passing char *test = "Hello there lol"; to your toArray(). Unfortunately, the string is not modifiable, so when you try to modify it with strtok(), you get a segmentation fault.
The simplest fix is:
char test[] = "Hello there lol";
You also have:
int *count = 0;
and you call the function with:
toArray(array, test, " ", count);
You need an integer, and to pass its address:
int count = 0;
...
toArray(array, test, " ", &count);
You were also trying to free the strings that were pointed at by the elements in array, but those were never allocated (they are parts of the string test). Don't free what was not allocated with malloc() et al.
With those fixes in place, this code works:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char **toArray(char **array, char str[], char sep[], int *count);
int main(void)
{
char test[] = "Hello there lol";
int count = 0;
char **array = malloc(sizeof(char *) * 5);
toArray(array, test, " ", &count);
printf("Count: %d\n", count);
for (int i = 0; i < count; i++)
printf("array %d: %s\n", i, array[i]);
free(array);
return 0;
}
char **toArray(char **array, char str[], char sep[], int *count)
{
char *temp = str;
temp = strtok(temp, sep);
array[0] = temp;
*count = 1;
while ((temp = strtok(NULL, sep)) != NULL)
array[(*count)++] = temp;
return array;
}
Output:
Count: 3
array 0: Hello
array 1: there
array 2: lol
I am doing an exercise on a book, changing the words in a sentence into pig latin. The code works fine in window 7, but when I compiled it in mac, the error comes out.
After some testings, the error comes from there. I don't understand the reason of this problem. I am using dynamic memories for all the pointers and I have also added the checking of null pointer.
while (walker != NULL && *walker != NULL){
free(**walker);
free(*walker);
free(walker);
walker++;
}
Full source code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define inputSize 81
void getSentence(char sentence [], int size);
int countWord(char sentence[]);
char ***parseSentence(char sentence[], int *count);
char *translate(char *world);
char *translateSentence(char ***words, int count);
int main(void){
/* Local definition*/
char sentence[inputSize];
int wordsCnt;
char ***head;
char *result;
getSentence(sentence, inputSize);
head = parseSentence(sentence, &wordsCnt);
result = translateSentence(head, wordsCnt);
printf("\nFinish the translation: \n");
printf("%s", result);
return 0;
}
void getSentence(char sentence [81], int size){
char *input = (char *)malloc(size);
int length;
printf("Input the sentence to big latin : ");
fflush(stdout);
fgets(input, size, stdin);
// do not copy the return character at inedx of length - 1
// add back delimater
length = strlen(input);
strncpy(sentence, input, length-1);
sentence[length-1]='\0';
free(input);
}
int countWord(char sentence[]){
int count=0;
/*Copy string for counting */
int length = strlen(sentence);
char *temp = (char *)malloc(length+1);
strcpy(temp, sentence);
/* Counting */
char *pToken = strtok(temp, " ");
char *last = NULL;
assert(pToken == temp);
while (pToken){
count++;
pToken = strtok(NULL, " ");
}
free(temp);
return count;
}
char ***parseSentence(char sentence[], int *count){
// parse the sentence into string tokens
// save string tokens as a array
// and assign the first one element to the head
char *pToken;
char ***words;
char *pW;
int noWords = countWord(sentence);
*count = noWords;
/* Initiaze array */
int i;
words = (char ***)calloc(noWords+1, sizeof(char **));
for (i = 0; i< noWords; i++){
words[i] = (char **)malloc(sizeof(char *));
}
/* Parse string */
// first element
pToken = strtok(sentence, " ");
if (pToken){
pW = (char *)malloc(strlen(pToken)+1);
strcpy(pW, pToken);
**words = pW;
/***words = pToken;*/
// other elements
for (i=1; i<noWords; i++){
pToken = strtok(NULL, " ");
pW = (char *)malloc(strlen(pToken)+1);
strcpy(pW, pToken);
**(words + i) = pW;
/***(words + i) = pToken;*/
}
}
/* Loop control */
words[noWords] = NULL;
return words;
}
/* Translate a world into big latin */
char *translate(char *word){
int length = strlen(word);
char *bigLatin = (char *)malloc(length+3);
/* translate the word into pig latin */
static char *vowel = "AEIOUaeiou";
char *matchLetter;
matchLetter = strchr(vowel, *word);
// consonant
if (matchLetter == NULL){
// copy the letter except the head
// length = lenght of string without delimiter
// cat the head and add ay
// this will copy the delimater,
strncpy(bigLatin, word+1, length);
strncat(bigLatin, word, 1);
strcat(bigLatin, "ay");
}
// vowel
else {
// just append "ay"
strcpy(bigLatin, word);
strcat(bigLatin, "ay");
}
return bigLatin;
}
char *translateSentence(char ***words, int count){
char *bigLatinSentence;
int length = 0;
char *bigLatinWord;
/* calculate the sum of the length of the words */
char ***walker = words;
while (*walker){
length += strlen(**walker);
walker++;
}
/* allocate space for return string */
// one space between 2 words
// numbers of space required =
// length of words
// + (no. of words * of a spaces (1) -1 )
// + delimater
// + (no. of words * ay (2) )
int lengthOfResult = length + count + (count * 2);
bigLatinSentence = (char *)malloc(lengthOfResult);
// trick to initialize the first memory
strcpy(bigLatinSentence, "");
/* Translate each word */
int i;
char *w;
for (i=0; i<count; i++){
w = translate(**(words + i));
strcat(bigLatinSentence, w);
strcat(bigLatinSentence, " ");
assert(w != **(words + i));
free(w);
}
/* free memory of big latin words */
walker = words;
while (walker != NULL && *walker != NULL){
free(**walker);
free(*walker);
free(walker);
walker++;
}
return bigLatinSentence;
}
Your code is unnecessarily complicated, because you have set things up such that:
n: the number of words
words: points to allocated memory that can hold n+1 char ** values in sequence
words[i] (0 <= i && i < n): points to allocated memory that can hold one char * in sequence
words[n]: NULL
words[i][0]: points to allocated memory for a word (as before, 0 <= i < n)
Since each words[i] points to stuff-in-sequence, there is a words[i][j] for some valid integer j ... but the allowed value for j is always 0, as there is only one char * malloc()ed there. So you could eliminate this level of indirection entirely, and just have char **words.
That's not the problem, though. The freeing loop starts with walker identical to words, so it first attempts to free words[0][0] (which is fine and works), then attempts to free words[0] (which is fine and works), then attempts to free words (which is fine and works but means you can no longer access any other words[i] for any value of i—i.e., a "storage leak"). Then it increments walker, making it more or less equivalent to &words[1]; but words has already been free()d.
Instead of using walker here, I'd use a loop with some integer i:
for (i = 0; words[i] != NULL; i++) {
free(words[i][0]);
free(words[i]);
}
free(words);
I'd also recommending removing all the casts on malloc() and calloc() return values. If you get compiler warnings after doing this, they usually mean one of two things:
you've forgotten to #include <stdlib.h>, or
you're invoking a C++ compiler on your C code.
The latter sometimes works but is a recipe for misery: good C code is bad C++ code and good C++ code is not C code. :-)
Edit: PS: I missed the off-by-one lengthOfResult that #David RF caught.
int lengthOfResult = length + count + (count * 2);
must be
int lengthOfResult = length + count + (count * 2) + 1; /* + 1 for final '\0' */
while (walker != NULL && *walker != NULL){
free(**walker);
free(*walker);
/* free(walker); Don't do this, you still need walker */
walker++;
}
free(words); /* Now */
And you have a leak:
int main(void)
{
...
free(result); /* You have to free the return of translateSentence() */
return 0;
}
In this code:
while (walker != NULL && *walker != NULL){
free(**walker);
free(*walker);
free(walker);
walker++;
}
You need to check that **walker is not NULL before freeing it.
Also - when you compute the length of memory you need to return the string, you are one byte short because you copy each word PLUS A SPACE (including a space after the last word) PLUS THE TERMINATING \0. In other words, when you copy your result into the bigLatinSentence, you will overwrite some memory that isn't yours. Sometimes you get away with that, and sometimes you don't...
Wow, so I was intrigued by this, and it took me a while to figure out.
Now that I figured it out, I feel dumb.
What I noticed from running under gdb is that the thing failed on the second run through the loop on the line
free(walker);
Now why would that be so. This is where I feel dumb for not seeing it right away. When you run that line, the first time, the whole array of char*** pointers at words (aka walker on the first run through) on the second run through, when your run that line, you're trying to free already freed memory.
So it should be:
while (walker != NULL && *walker != NULL){
free(**walker);
free(*walker);
walker++;
}
free(words);
Edit:
I also want to note that you don't have to cast from void * in C.
So when you call malloc, you don't need the (char *) in there.