C Beginner - Pointers - c

I'm having a little trouble understanding where my code goes wrong. I want to store into an array of strings multiple lines and after to display them; for some unknown reason after I enter a different number of lines ( let's say 5 ), it will only display the last line 5 times. Any idea?
Thank you
#include <stdio.h>
#include <string.h>
int readLine(char line[], int max);
void printLines(char *lines[], int size);
int main(){
char *lines[100];
char line[100];
int i = 0;
int len = 0;
char *p;
while( (len = readline(line,100)) > 0){
if((p = malloc(len * sizeof(char))) != NULL){
p = line;
lines[i++] = p;
}
}
lines[i] = '\0';
printLines(lines, i);
return 0;
}
int readline(char line[], int max){
if(fgets(line,max,stdin) == NULL)
return 0;
printf("%d \n", strlen(line));
return strlen(line);
}
void printLines(char *lines[], int size){
int i;
for(i = 0; i < size; i++)
printf("%s\n", lines[i]);
}

if((p = malloc(len * sizeof(char))) != NULL){
p = line;
lines[i++] = p;
You allocate memory for a string, and store the pointer returned from malloc() in p. Then you store a pointer to line in p, effectively throwing away the pointer to the memory you just allocated. You need to copy the string by using strcpy() or similar.
strcpy (p, line);

This is your problem:
if((p = malloc(len * sizeof(char))) != NULL){
p = line;
lines[i++] = p;
}
p is a pointer that points to allocated storage first, but then its value is overwritten with the value of line. What you want is to copy what is currently stored at the location that line points to to the location where p points to. The function for that is strcpy().
Notes:
sizeof (char) is by the very definition of sizeof exactly 1.
You will have buffer overflow issues (google that term!) if people enter lines longer than 100 chars.
You are not really handling malloc() failure but merely skipping some code and otherwise ignoring it. Write an error message and call exit() for now if malloc() fails. Wrap that in a function for easier reuse (ofter called xalloc()).

Related

Novice C question: Working with a variable-length array of variable-length strings?

I probably got an easy one for the C programmers out there!
I am trying to create a simple C function that will execute a system command in and write the process output to a string buffer out (which should be initialized as an array of strings of length n). The output needs to be formatted in the following way:
Each line written to stdout should be initialized as a string. Each of these strings has variable length. The output should be an array consisting of each string. There is no way to know how many strings will be written, so this array is also technically of variable length (but for my purposes, I just create a fixed-length array outside the function and pass its length as an argument, rather than going for an array that I would have to manually allocate memory for).
Here is what I have right now:
#define MAX_LINE_LENGTH 512
int exec(const char* in, const char** out, const size_t n)
{
char buffer[MAX_LINE_LENGTH];
FILE *file;
const char terminator = '\0';
if ((file = popen(in, "r")) == NULL) {
return 1;
}
for (char** head = out; (size_t)head < (size_t)out + n && fgets(buffer, MAX_LINE_LENGTH, file) != NULL; head += strlen(buffer)) {
*head = strcat(buffer, &terminator);
}
if (pclose(file)) {
return 2;
}
return 0;
}
and I call it with
#define N 128
int main(void)
{
const char* buffer[N];
const char cmd[] = "<some system command resulting in multi-line output>";
const int code = exec(cmd, buffer, N);
exit(code);
}
I believe the error the above code results in is a seg fault, but I'm not experienced enough to figure out why or how to fix.
I'm almost positive it is with my logic here:
for (char** head = out; (size_t)head < (size_t)out + n && fgets(buffer, MAX_LINE_LENGTH, file) != NULL; head += strlen(buffer)) {
*head = strcat(buffer, &terminator);
}
What I thought this does is:
Get a mutable reference to out (i.e. the head pointer)
Save the current stdout line to buffer (via fgets)
Append a null terminator to buffer (because I don't think fgets does this?)
Overwrite the data at head pointer with the value from step 3
Move head pointer strlen(buffer) bytes over (i.e. the number of chars in buffer)
Continue until fgets returns NULL or head pointer has been moved beyond the bounds of out array
Where am I wrong? Any help appreciated, thanks!
EDIT #1
According to Barmar's suggestions, I edited my code:
#include <stdio.h>
#include <stdlib.h>
#define MAX_LINE_LENGTH 512
int exec(const char* in, const char** out, const size_t n)
{
char buffer[MAX_LINE_LENGTH];
FILE *file;
if ((file = popen(in, "r")) == NULL) return 1;
for (size_t i = 0; i < n && fgets(buffer, MAX_LINE_LENGTH, file) != NULL; i += 1) out[i] = buffer;
if (pclose(file)) return 2;
return 0;
}
#define N 128
int main(void)
{
const char* buffer[N];
const char cmd[] = "<system command to run>";
const int code = exec(cmd, buffer, N);
for (int i = 0; i < N; i += 1) printf("%s", buffer[i]);
exit(code);
}
While there were plenty of redundancies with what I wrote that are now fixed, this still causes a segmentation fault at runtime.
Focusing on the edited code, this assignment
out[i] = buffer;
has problems.
In this expression, buffer is implicitly converted to a pointer-to-its-first-element (&buffer[0], see: decay). No additional memory is allocated, and no string copying is done.
buffer is rewritten every iteration. After the loop, each valid element of out will point to the same memory location, which will contain the last line read.
buffer is an array local to the exec function. Its lifetime ends when the function returns, so the array in main contains dangling pointers. Utilizing these values is Undefined Behaviour.
Additionally,
for (int i = 0; i < N; i += 1)
always loops to the maximum storable number of lines, when it is possible that fewer lines than this were read.
A rigid solution uses an array of arrays to store the lines read. Here is a cursory example (see: this answer for additional information on using multidimensional arrays as function arguments).
#include <stdio.h>
#include <stdlib.h>
#define MAX_LINES 128
#define MAX_LINE_LENGTH 512
int exec(const char *cmd, char lines[MAX_LINES][MAX_LINE_LENGTH], size_t *lc)
{
FILE *stream = popen(cmd, "r");
*lc = 0;
if (!stream)
return 1;
while (*lc < MAX_LINES) {
if (!fgets(lines[*lc], MAX_LINE_LENGTH, stream))
break;
(*lc)++;
}
return pclose(stream) ? 2 : 0;
}
int main(void)
{
char lines[MAX_LINES][MAX_LINE_LENGTH];
size_t n;
int code = exec("ls -al", lines, &n);
for (size_t i = 0; i < n; i++)
printf("%s", lines[i]);
return code;
}
Using dynamic memory is another option. Here is a basic example using strdup(3), lacking robust error handling.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char **exec(const char *cmd, size_t *length)
{
FILE *stream = popen(cmd, "r");
if (!stream)
return NULL;
char **lines = NULL;
char buffer[4096];
*length = 0;
while (fgets(buffer, sizeof buffer, stream)) {
char **reline = realloc(lines, sizeof *lines * (*length + 1));
if (!reline)
break;
lines = reline;
if (!(lines[*length] = strdup(buffer)))
break;
(*length)++;
}
pclose(stream);
return lines;
}
int main(void)
{
size_t n = 0;
char **lines = exec("ls -al", &n);
for (size_t i = 0; i < n; i++) {
printf("%s", lines[i]);
free(lines[i]);
}
free(lines);
}

realloc(): invalid next size: followed by a 32bit register

so I've been writing an mtf encoder in C and I've been running into a realloc() error regardless of what I do. I've checked to see if there was an error in my logic (and there may be) by using print statements to see if I'm overstepping the bounds of my currently malloc'd array (adding a string past my original array size) and that doesn't seem to be the issue. I've used GDB and Valgrind and GDB gives me a cryptic message while Valgrind runs into a segmentation fault. This is my first time using dynamic memory and I'm pretty confused as to what the problem is, below are my code along with the GDB error:
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int count = 0;
move_to_front(int index, char** words){
int i;
char *t = words[index];
for(i = index; i>1; i--){
words[i] = words[i-1];
}
words[1] = t;
}
char** reallocate_words(char** words, int* words_size_pointer){
printf("We're entering here\n");
printf("%d", *words_size_pointer);
int temp = *words_size_pointer;
char** tempor;
temp = temp*2;
printf("%d", temp);
tempor = (char**) realloc(words, temp);
int i = *words_size_pointer;
for(i; i<temp; i++){
tempor[i] = (char*) malloc(120);
}
words_size_pointer = &temp;
return tempor;
}
void encode_word(int* words_size_pointer, FILE *f, char* word, char** words){
if(count == 0){
words[1] = word;
fputs(words[1], f);
count++;
}
int i;
for(i=0; i<=count; i++){
if(strcmp(words[i], word) == 0){
break;
}
}
if(i>=(*words_size_pointer)){
printf("%d\n", i);
words = reallocate_words(words, words_size_pointer);
words[count+1] = word;
count++;
fputc(count+128, f);
fputs(words[count], f);
move_to_front(count, words);
}
if(i>count){
words[count+1] = word;
count++;
fputc(count+128, f);
fputs(words[count], f);
move_to_front(count, words);
}
else{
fputc(i+128, f);
move_to_front(i, words);
}
}
void sep_words(char** words, char *line, int* words_size_pointer, FILE *f){
char* x;
int i = 0;
x = strtok(line, " ");
while(x != NULL){
encode_word(words_size_pointer,f, x, words);
x = strtok(NULL, " ");
}
}
void readline(FILE *f_two, FILE *f, char** words, int* words_size_pointer){
char *line;
size_t len = 0;
ssize_t temp;
int count;
do{
temp = getline(&line,&len,f);
printf("%s", line);
if(temp!= -1){
sep_words(words, line, words_size_pointer, f_two);
}
}while(temp!=-1);
}
int main(int argc, char *argv[]){
int x;
int i;
int j;
x = strlen(argv[1]);
char fi[x];
char mtf[3] = "mtf";
FILE *f;
FILE *f_two;
for(j = 0; j<(x-3); j++){
fi[j] = argv[1][j];
}
strcat(fi, mtf);
f = fopen(argv[1], "r");
f_two = fopen(fi, "w");
fputc(0xFA, f_two);
fputc(0XCE, f_two);
fputc(0XFA, f_two);
fputc(0XDF, f_two);
if(f == NULL){
return 1;
}
char** words;
words = (char **) malloc(20);
for(i = 0; i<20; i++){
words[i] = (char*) malloc(120);
}
int words_size = 20;
int* words_size_pointer = &words_size;
readline(f_two, f, words, words_size_pointer);
return 0;
}
And as for the GDB error:
*** Error in `/file_loc/mtfcoding2': realloc(): invalid next size: 0x0000000000603490 ***
2040 \\This is due to print statements within my function.
Program received signal SIGABRT, Aborted.
0x00007ffff7a4acc9 in __GI_raise (sig=sig#entry=6)
at ../nptl/sysdeps/unix/sysv/linux/raise.c:56
56 ../nptl/sysdeps/unix/sysv/linux/raise.c: No such file or directory.
Thank you for your time! :)
malloc and realloc require the number of bytes as argument. However you are writing code like:
char** words;
words = (char **) malloc(20);
for(i = 0; i<20; i++){
words[i] = (char*) malloc(120);
You allocate 20 bytes but then you write 20 pointers (which probably takes 80 bytes). To fix this you need to compute how many bytes are required to store the 20 pointers. A safe way of doing this is to use malloc as recommended by SO:
words = malloc(20 * sizeof *words);
You have the same problem in your realloc call.
This line has no effect: words_size_pointer = &temp; . Perhaps you meant *words_size_pointer = temp; . Make sure you clearly understand the difference between those two lines.
NB. There may be other errors.
Well, for starters, your move_to_front is dropping pointers. This one is a pretty bad memory leak, and given the nature of C and memory leaks, could be the cause of your segfault (for now). You should be doing this
for(i = index; i > 1; i--){
char* tmp = words[i];
words[i] = words[i-1];
words[i-1] = tmp;
}
Otherwise, what you have done is overwritten the pointers from index to words[2] with the pointer at index. Also, you seem to like to start your words array at 1 instead of 0. Those off-by-one errors are gonna hurt ya too.
Also (as stated in my earlier comment), words_size_pointer = &temp; isn't quite right. Do this instead *words_size_pointer = temp;. The first way is only a local pointer re-assignment, but you want the change to be reflected in the caller's scope, so you must dereference the pointer and modify it.
It seems to be caused by your call to getline in your readline function.
char *line;
size_t len = 0;
...
temp = getline(&line,&len,f);
getline requires line to be NULL (in which case the value of len is ignored) or line must be a pointer returned by malloc, calloc, or realloc. If line is not NULL, and len isn't large enough, line is resized by calling realloc. This is the crucial point: line points to some random address that wasn't returned by malloc, so getline attempts to use realloc to increase the buffer size because 0 bytes is just too small.
You also have a buffer overflow here:
char mtf[3] = "mtf";
...
for(j = 0; j<(x-3); j++){
fi[j] = argv[1][j];
}
strcat(fi, mtf);
Because you made mtf only 3 bytes in size, it may or may not be followed immediately by a null terminator. When strcat is called, if mtf isn't followed immediately by a 0 byte in memory, you end up with something like sample.mtf\x1b\x01X as the output filename, assuming you don't write too far beyond the end of the fi array to crash the program with a SIGSEGV (segfault). Any of the following will correct it:
char mtf[4] = "mtf";
//OR
char mtf[] = "mtf";
//OR
const char *mtf = "mtf";
However, because fi is only made up of x number of bytes, you'll end up writing to fi[x] with your null terminator that strcat adds. This is a problem because char fi[x]; means you only have array indices 0 to x - 1 available. Fix this part by using x = strlen(argv[1]) + 1.

realloc: invalid next size and malloc: memory corruption (fast)

I am doing an exercise for fun from K and R C programming book. The program is for finding the longest line from a set of lines entered by the user and then prints it.
Inputs:
This is a test
This is another long test
this is another long testthis is another long test
Observation:
It runs fine for the first two inputs but fails for the larger string (3rd input)
Errors:
Error in `./longest': realloc(): invalid next size: 0x000000000246e010 ***
Error in `./longest': malloc(): memory corruption (fast): 0x000000000246e030 ***
My efforts:
I have been trying to debug this since 2 days (rubber duck debugging) but the logic seems fine. GDB points to the realloc call in the _getline function and shows a huge backtrace with glibc.so memory allocation calls at the top.
Here is what I have written (partially, some part is taken from the book directly):-
#include <stdio.h>
#include <stdlib.h>
int MAXLINE = 10;
int INCREMENT = 10;
char* line = NULL, *longest = NULL;
void _memcleanup(){
free(line);
free(longest);
}
void copy(char longest[], char line[]){
int i=0;
char* temp = realloc(longest,(MAXLINE)*sizeof(char));
if(temp == NULL){
printf("%s","Unable to allocate memory");
_memcleanup();
exit(1);
}
longest = temp;
while((longest[i] = line[i]) != '\0'){
++i;
}
}
int _getline(char s[]){
int i,c;
for(i=0; ((c=getchar())!=EOF && c!='\n'); i++){
if(i == MAXLINE - 1){
char* temp = realloc(s,(MAXLINE + INCREMENT)*sizeof(char));
if(temp == NULL){
printf("%s","Unable to allocate memory");
_memcleanup();
exit(1);
}
s= temp;
MAXLINE += INCREMENT;
}
s[i] = c;
}
if(c == '\n'){
s[i++] = c;
}
s[i]= '\0';
return i;
}
int main(){
int max=0, len;
line = malloc(MAXLINE*sizeof(char));
longest = malloc(MAXLINE*sizeof(char));
while((len = _getline(line)) > 0){
printf("%d%d", len, MAXLINE);
if(len > max){
max = len;
copy(longest, line);
}
}
if(max>0){
printf("%s",longest);
}
_memcleanup();
return 0;
}
You´re reallocating on copied addresses (because parameters).
A parameter in C is a copy of the original value everytime; in case of
a pointer it will point to the same location but the address itself is copied.
realloc resizes the buffer asociated with the address, everything fine so far.
But it can relocate the whole thing and assign a completely new address,
and this new address (if it happens) will be lost after the function returns to main.
Use a double pointer:
Pass a char **s instead of char *s (==char s[]) as formal parameter,
pass &xyz intead of xyz as actual value, and inside the function,
use *xyz and **xyz (or (*xyz)[index]) for address and value.
Other things:
Global variables are ugly (and confusing when named same as parameters),
multiplying with sizeof(char) is nonsense because it´s be 1 everytime,
and names in capitals should be used for #define´s rather than variables.
The double pointer alone, isn't the solution to your problems. You have 2 primary issues. You can see them by entering your strings as a string of characters and will notice you problem occurs when you pass the 20th character. (e.g. 01234567890123456789)
You have declared both line and longest globally. So while you can rewrite _getline (char **s), you can also simply update line at the end of _getline with memcpy (include string.h). For example:
memcpy (line, s, (size_t)i);
return i;
}
That cures your _getline issue. Issue two is fairly straight forward. You are not null-terminating longest in copy. (your choice of arguments with the same name as the globals presents challenges as well) Including the following fixes copy:
++i;
}
longest[i] = '\0';
}
If you incorporate both changes, then I believe you will find you routine works. You can then rewite _getline (char **s) and pass &line as another exercise. For example, you can rewrite _getline as:
int
_getline (char **s) {
int i, c;
for (i = 0; ((c = getchar ()) != EOF && c != '\n'); i++) {
if (i == MAXLINE - 1) {
char *temp = realloc (*s, (MAXLINE + INCREMENT) * sizeof (char));
if (temp == NULL) {
printf ("%s", "Unable to allocate memory");
_memcleanup ();
exit (1);
}
*s = temp;
MAXLINE += INCREMENT;
}
(*s)[i] = c;
}
if (c == '\n') {
(*s)[i++] = c;
}
(*s)[i] = '\0';
return i;
}
And then modify your call in main to:
while ((len = _getline (&line)) > 0) {

Allocating multidimensional char pointer in C

I am trying to read lines from a file and store them in a multidimensional char pointer. When I run my code, it runs without errors, however, the lines will not be printed correctly in my main() function, but prints correctly in the getline() function. Can anybody explain what is happening and how I correctly store them in my multidimensional pointer?
My code:
int main (int argc, const char * argv[]) {
char **s = (char**) malloc(sizeof(char*) * 1000);
int i = 0;
while (getline(s[i]) != -1){
printf("In array: %s \n", s[i]);
i++;
}
return 0;
}
int getline(char *s){
s = malloc(sizeof(char*) * 1000);
int c, i = 0;
while ((c = getchar()) != '\n' && c != EOF) {
s[i++] = c;
}
s[i] = '\0';
printf("String: %s \n", s);
if (c == EOF) {
return -1;
}
return 0;
}
and my output:
String: First line
<br>In array: (null)
<br>String: Second line
<br>In array: (null)
<br>String: Third line
<br>In array: (null)
You are passing by value. Even though you change the value of *s in getline(), main does not see it. You have to pass the address of s[i] so that getline() can change it:
int getline(char **s) {
* s= malloc( sizeof(char) * 1000) ;
...
}
Also, if you want to be a bit more efficient with memory, read the line into a local buffer (of size 1000) if you want. Then when you are done reading the line, allocate only the memory you need to store the actual string.
int getline( char ** s )
{
char tmpstr[1000] ;
...
tmpstr[i++]= c ;
}
tmpstr[i]= '\0' ;
* s= strdup( tmpstr) ;
return 0 ;
}
If you want to improve things even further, take a step back and thing about a few things. 1) allocating the two parts of the multi-dimensional array in two different functions is going to make it harder for others to understand. 2) passing in a temporary string from outside to getline() would allow it to be significantly simpler:
int main()
{
char ** s= (char **) malloc( 1000 * sizeof(char *)) ;
char tmpstr[1000] ;
int i ;
while ( -1 != getline( tmpstr))
{
s[i ++]= strdup( tmpstr) ;
}
return 0 ;
}
int getline( char * s)
{
int c, i = 0 ;
while (( '\n' != ( c= getchar())) && ( EOF != c )) { s[i ++]= c ; }
s[i]= '\0' ;
return ( EOF == c ) ? -1 : 0 ;
}
Now, getline is just about IO, and all the allocation of s is handled in one place, and thus easier to reason about.
The problem is that this line inside getline function
s = malloc(sizeof(char) * 1000); // Should be sizeof(char), not sizeof(char*)
has no effect on the s[i] pointer passed in. This is because pointers are passed by value.
You have two choices here:
Move your memory allocation into main, and keep passing the pointer, or
Keep your allocation in getline, but pass it a pointer to pointer from main.
Here is how you change main for the first option:
int main (int argc, const char * argv[]) {
char **s = malloc(sizeof(char*) * 1000);
int i = 0;
for ( ; ; ) {
s[i] = malloc(sizeof(char) * 1000);
if (getline(s[i]) == -1) break;
printf("In array: %s \n", s[i]);
i++;
}
return 0;
}
My advise: completely avoid writing your own getline() function, and avoid all fixed size buffers. In the POSIX.1-2008 standart, there is already a getline() function. So you can do this:
//Tell stdio.h that we want POSIX.1-2008 functions
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
int main() {
char** s = NULL;
int count = 0;
do {
count++;
s = realloc(s, count*sizeof(char*));
s[count-1] = NULL; //so that getline() will allocate the memory itself
} while(getline(&s[count-1], NULL, stdin));
for(int i = 0; i < count; i++) printf(s[i]);
}
Why do you use malloc in the first place. malloc is very very dangerous!!
Just use s[1000][1000] and pass &s[0][0] for the first line, &s[1][0] for the second line etc.
For printing printf("%s \n", s[i]); where i is the line you want.

"Pointer being freed was not allocated" happen on mac but not on window7

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.

Resources