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.
Related
i'm trying to implement little program that takes a text and breaks it into lines and sort them in alphabetical order but i encountered a little problem, so i have readlines function which updates an array of pointers called lines, the problem is when i try to printf the first pointer in lines as an array using %s nothing is printed and there is no errors.
I have used strcpy to copy an every single text line(local char array) into a pointer variable and then store that pointer in lines array but it gave me the error.
Here is the code:
#include <stdio.h>
#define MAXLINES 4
#define MAXLENGTH 1000
char *lines[MAXLINES];
void readlines() {
int i;
for (i = 0; i < MAXLINES; i++) {
char c, line[MAXLENGTH];
int j;
for (j = 0; (c = getchar()) != '\0' && c != '\n' && j < MAXLENGTH; j++) {
line[j] = c;
}
lines[i] = line;
}
}
int main(void) {
readlines();
printf("%s", lines[0]);
getchar();
return 0;
}
One problem is the following line:
lines[i] = line;
In this line, you make lines[i] point to line. However, line is a local char array whose lifetime ends as soon as the current loop iteration ends. Therefore, lines[i] will contain a dangling pointer (i.e. a pointer to an object that is no longer valid) as soon as the loop iteration ends.
For this reason, when you later call
printf("%s", lines[0]);
lines[0] is pointing to an object whose lifetime has ended. Dereferencing such a pointer invokes undefined behavior. Therefore, you cannot rely on getting any meaningful output, and your program may crash.
One way to fix this would be to not make lines an array of pointers, but rather an multidimensional array of char, i.e. an array of strings:
char lines[MAXLINES][MAXLENGTH+1];
Now you have a proper place for storing the strings, and you no longer need the local array line in the function readlines.
Another issue is that the line
printf("%s", lines[0]);
requires that lines[0] points to a string, i.e. to an array of characters terminated by a null character. However, you did not put a null character at the end of the string.
After fixing all of the issues mentioned above, your code should look like this:
#include <stdio.h>
#define MAXLINES 4
#define MAXLENGTH 1000
char lines[MAXLINES][MAXLENGTH+1];
void readlines() {
int i;
for (i = 0; i < MAXLINES; i++) {
char c;
int j;
for (j = 0; (c = getchar()) != '\0' && c != '\n' && j < MAXLENGTH; j++) {
lines[i][j] = c;
}
//add terminating null character
lines[i][j] = '\0';
}
}
int main(void) {
readlines();
printf("%s", lines[0]);
return 0;
}
However, this code still has a few issues, which are probably unrelated to your immediate problem, but could cause trouble later:
The function getchar will return EOF, not '\0', when there is no more data (or when an error occurred). Therefore, you should compare the return value of getchar with EOF instead of '\0'. However, a char is not guaranteed to be able to store the value of EOF. Therefore, you should store the return value of getchar in an int instead. Note that getchar returns a value of type int, not char.
When j reaches MAX_LENGTH, you will call getchar one additional time before terminating the loop. This can cause undesired behavior, such as your program waiting for more user input or an important character being discarded from the input stream.
In order to also fix these issues, I recommend the following code:
#include <stdio.h>
#define MAXLINES 4
#define MAXLENGTH 1000
char lines[MAXLINES][MAXLENGTH+1];
void readlines() {
int i;
for (i = 0; i < MAXLINES; i++)
{
//changed type from "char" to "int"
int c;
int j;
for ( j = 0; j < MAXLENGTH; j++ )
{
if ( (c = getchar()) == EOF || c == '\n' )
break;
lines[i][j] = c;
}
//add terminating null character
lines[i][j] = '\0';
}
}
int main(void) {
readlines();
printf("%s", lines[0]);
return 0;
}
Problem 1
char *lines[MAXLINES];
For the compiler it makes no difference how you write this, but for you, as you are learning C, maybe it is worth consider different spacing and naming. Question is: what is lines[]? lines[] is supposed to be an array of strings and hold some text inside. So lines[0] is a string, lines[1] is a string and so on. As pointed in a comment you could also use char lines[MAX_LINES][MAX_LENGTH] and have a 2D box of NxM char. This way you would have a pre-determined size in terms of number and size of lines and have simpler things at a cost of wasting space in lines of less than MAX_LENGTH chars and having a fixed number of lines you can use, but no need to allocate memory.
A more flexible way is to use an array of pointers. Since each pointer will represent a line, a single one
char* line[MAXLINES];
is a better picture of the use: line[0] is char*, line[1] is char* and so on. But you will need to allocate memory for each line (and you did not) in your code.
Remember int main(int argc, char**argv)
This is the most flexible way, since in this way you can hold any number of lines. The cost? Additional allocations.
size_t n_lines;
char** line;
This may be the best representation, as known by every C program since K&R.
Problem 2
for (
j = 0;
(c = getchar()) != '\0' && c != '\n' && j < MAXLENGTH;
j++) {
line[j] = c;
}
lines[i] = line;
This loop does not copy the final 0 that terminates each string. And reuses the same line, a char[] to hold the data as being read. And the final line does not copy a string, if one existed there. There is no one since the final 0 was stripped off by the loop. And there is no data too, since the area is being reused.
A complete C example of uploading a file to a container in memory
I will let an example of a more controlled way of writing this, a container for a set of lines and even a sorting function.
a data structure
The plan is to build an array of pointers as the system does for main. Since we do no know ahead the number of lines and do not want this limitation we will allocate memory in groups of blk_size lines. At any time we have limit pointers to use. From these size are in use. line[] is char* and points to a single line of text. The struct is
typedef struct
{
size_t blk_size; // block
size_t limit; // actual allocated size
size_t size; // size in use
char** line; // the lines
} Block;
the test function
Block* load_file(const char*);
Plan is to call load_file("x.txt") and the function returns a Block* pointing to the array representing the lines in file, one by one. Then we call qsort() and sort the whole thing. If the program is called lines we will run
lines x.txt
and it will load the file x.txt, show its contents on screen, sort it, show the sorted lines and then erase everything at exit.
main() for the test
int main(int argc, char** argv)
{
char msg[80] = {0};
if (argc < 2) usage();
Block* test = load_file(argv[1]);
sprintf(msg, "==> Loading \"%s\" into memory", argv[1]);
status_blk(test, msg);
qsort(test->line, test->size, sizeof(void*), cmp_line);
sprintf(msg, "==> \"%s\" after sort", argv[1]);
status_blk(test, msg);
test = delete_blk(test);
return 0;
};
As planned
load_file() is the constructor and load the file contents into a Block.
status_blk() shows the contents and accepts a convenient optional message
qsort() sorts the lines using a one-line cmp_line() function.
status_blk() is called again and shows the now sorted contents
as in C++ delete_blk() is the destructor and erases the whole thing._
output using main() as tlines.c for testing
PS M:\> .\lines tlines.c
loading "tlines.c" into memory
Block extended for a total of 16 pointers
==> Loading "tlines.c" into memory
Status: 13 of 16 lines. [block size is 8]:
1 int main(int argc, char** argv)
2 {
3 char msg[80] = {0};
4 if (argc < 2) usage();
5 Block* test = load_file(argv[1]);
6 sprintf(msg, "==> Loading \"%s\" into memory", argv[1]);
7 status_blk(test, msg);
8 qsort(test->line, test->size, sizeof(void*), cmp_line);
9 sprintf(msg, "==> \"%s\" after sort", argv[1]);
10 status_blk(test, msg);
11 test = delete_blk(test);
12 return 0;
13 };
==> "tlines.c" after sort
Status: 13 of 16 lines. [block size is 8]:
1 Block* test = load_file(argv[1]);
2 char msg[80] = {0};
3 if (argc < 2) usage();
4 qsort(test->line, test->size, sizeof(void*), cmp_line);
5 return 0;
6 sprintf(msg, "==> Loading \"%s\" into memory", argv[1]);
7 sprintf(msg, "==> \"%s\" after sort", argv[1]);
8 status_blk(test, msg);
9 status_blk(test, msg);
10 test = delete_blk(test);
11 int main(int argc, char** argv)
12 {
13 };
About the code
I am not sure if it needs much explanation, it is a single function that does the file loading and it has around 20 lines of code. The other functions has less than 10. The whole file is represented in line that is char** and Block has the needed info about actual size.
Since line[] is an array of pointers we can call
qsort(test->line, test->size, sizeof(void*), cmp_line);
and use
int cmp_line(const void* one, const void* other)
{
return strcmp(
*((const char**)one), *((const char**)other));
}
using strcmp() to compare the strings and have the lines sorted.
create_blk() accepts a block size for use in the calls to realloc() for eficiency.
Delete a Block is a 3-step free() in the reverse order of allocation.
The complete code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
size_t blk_size; // block
size_t limit; // actual allocated size
size_t size; // size in use
char** line; // the lines
} Block;
Block* create_blk(size_t);
Block* delete_blk(Block*);
int status_blk(Block*, const char*);
Block* load_file(const char*);
int cmp_line(const void*, const void*);
void usage();
int main(int argc, char** argv)
{
char msg[80] = {0};
if (argc < 2) usage();
Block* test = load_file(argv[1]);
sprintf(msg, "\n\n==> Loading \"%s\" into memory", argv[1]);
status_blk(test, msg);
qsort(test->line, test->size, sizeof(void*), cmp_line);
sprintf(msg, "\n\n==> \"%s\" after sort", argv[1]);
status_blk(test, msg);
test = delete_blk(test);
return 0;
};
int cmp_line(const void* one, const void* other)
{
return strcmp(
*((const char**)one), *((const char**)other));
}
Block* create_blk(size_t size)
{
Block* nb = (Block*)malloc(sizeof(Block));
if (nb == NULL) return NULL;
nb->blk_size = size;
nb->limit = size;
nb->size = 0;
nb->line = (char**)malloc(sizeof(char*) * size);
return nb;
}
Block* delete_blk(Block* blk)
{
if (blk == NULL) return NULL;
for (size_t i = 0; i < blk->size; i += 1)
free(blk->line[i]); // free lines
free(blk->line); // free block
free(blk); // free struct
return NULL;
}
int status_blk(Block* bl,const char* msg)
{
if (msg != NULL) printf("%s\n", msg);
if (bl == NULL)
{
printf("Status: not allocated\n");
return -1;
}
printf(
"Status: %zd of %zd lines. [block size is %zd]:\n",
bl->size, bl->limit, bl->blk_size);
for (int i = 0; i < bl->size; i += 1)
printf("%4d\t%s", 1 + i, bl->line[i]);
return 0;
}
Block* load_file(const char* f_name)
{
if (f_name == NULL) return NULL;
fprintf(stderr, "loading \"%s\" into memory\n", f_name);
FILE* F = fopen(f_name, "r");
if (F == NULL) return NULL;
// file is open
Block* nb = create_blk(8); // block size is 8
char line[200];
char* p = &line[0];
p = fgets(p, sizeof(line), F);
while (p != NULL)
{
// is block full?
if (nb->size >= nb->limit)
{
const size_t new_sz = nb->limit + nb->blk_size;
char* new_block =
realloc(nb->line, (new_sz * sizeof(char*)));
if (new_block == NULL)
{
fprintf(
stderr,
"\tCould not extend block to %zd "
"lines\n",
new_sz);
break;
}
printf(
"Block extended for a total of %zd "
"pointers\n",
new_sz);
nb->limit = new_sz;
nb->line = (char**)new_block;
}
// now copy the line
nb->line[nb->size] = (char*)malloc(1 + strlen(p));
strcpy(nb->line[nb->size], p);
nb->size += 1;
// read next line
p = fgets(p, sizeof(line), F);
}; // while()
fclose(F);
return nb;
}
void usage()
{
fprintf(stderr,"Use: program file_to_load\n");
exit(EXIT_FAILURE);
}
Try something like this:
#include <stdio.h>
#include <stdlib.h> // for malloc(), free(), exit()
#include <string.h> // for strcpy()
#define MAXLINES 4
#define MAXLENGTH 1000
char *lines[MAXLINES];
void readlines() {
for( int i = 0; i < MAXLINES; i++) {
char c, line[MAXLENGTH + 1]; // ALWAYS one extra to allow for '\0'
int j = 0;
// RE-USE(!) local array for input characters until NL or length
// NB: Casting return value to character (suppress warning)
while( (c = (char)getchar()) != '\0' && c != '\n' && j < MAXLENGTH )
line[ j++ ] = c;
line[j] = '\0'; // terminate array (transforming it to 'string')
// Attempt to get a buffer to preserve this line
// (Old) compiler insists on casting return from malloc()
if( ( lines[i] = (char*)malloc( (j + 1) * sizeof lines[0][0] ) ) == NULL ) {
fprintf( stderr, "malloc failure\n" );
exit( -1 );
}
strcpy( lines[i], line ); // preserve this line
}
}
int my_main() {
readlines(); // only returns after successfully reading 4 lines of input
for( int i = 0; i < MAXLINES; i++)
printf( "Line %d: '%s'\n", i, lines[i] ); // enhanced
/* Maybe do stuff here */
for( int j = 0; j < MAXLINES; j++) // free up allocated memory.
free( lines[j] );
return 0;
}
If you would prefer to 'factor out` some code (and have a facility that you've written is absent, here's a version:
char *my_strdup( char *str ) {
int len = strlen( str ) + 1; // ALWAYS +1
// Attempt to get a buffer to preserve this line
// (Old) compiler insists on casting return from malloc()
char *pRet = (char*)malloc( len * sizeof *pRet );
if( pRet == NULL ) {
fprintf( stderr, "malloc failure\n" );
exit( -1 );
}
return strcpy( pRet, str );
}
The the terminating and preserve is condensed to:
line[j] = '\0'; // terminate array (transforming it to 'string')
lines[i] = my_strdup( line ); // preserve this line
i've this code:
int i =0;
char * str = "ar bitrary whitespace";
int whitespace=0,index;
for(index = 0;index < strlen(str);index++)
{
if(isspace(str[index]) != 0)
{
whitespace++;
}
}
char * tmp = (char *)calloc(strlen(str)-whitespace +1,sizeof(char));
memset(tmp,'\0',strlen(tmp)+1);
while(i < strlen(str))
{
if(isspace(str[i]) != 0)
{
i++;
continue;
}else if(isspace(str[i]) == 0)
{
strcat(tmp,&str[i]);
i++;
}
}
printf("\nnew string is: %s \n",tmp);
the problem is that the output is a string without the whitespace removed + some garbage character.
I've used memset to null terminate tmp,is there the problem?
The length of the source string could be calculated before this loop
for(index = 0;index < strlen(str);index++)
Otherwise if the code will not be optimized the function strlen will be called for each iteration of the loop. In fact using of the function is redundant for such a task.
This statement
memset(tmp,'\0',strlen(tmp)+1);
does not make sense because the call of calloc already initialized the memory with zeroes.
This statement
strcat(tmp,&str[i]);
also copies blanks from the source string after the position i. So it can write beyond the memory allocated for the array pointed to by the pointer tmp.
You can write a separate function that can look as it is shown in this demonstrative program
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
char * remove_blanks( const char *s )
{
size_t n = 0;
const char *p = s;
do
{
if ( !isspace( ( unsigned char )*p ) ) ++n;
} while ( *p++ );
char *t = malloc( n );
if ( t )
{
char *q = t;
p = s;
do
{
if ( !isspace( ( unsigned char )*p ) ) *q++ = *p;
} while ( *p++ );
}
return t;
}
int main(void)
{
char * str = "ar bitrary whitespace";
printf( "\"%s\"\n", str );
char *t = remove_blanks( str );
printf( "\"%s\"\n", t );
free( t );
}
The program output is
"ar bitrary whitespace"
"arbitrarywhitespace"
this is your problem
memset(tmp,'\0',strlen(tmp)+1);
strlen(tmp) works by looking for '\0' in tmp, you have a chicken and egg situation here.
You should not be doing a memset any way, just tack on a '\0' when you fnish copying
And dont use strcat, instead maintain a pointer to tmp and just do *p = str[i] then increment p
I will not read your question, you overwrite the '\0' terminator for sure.
Now that I read your question, it looks like you need to understand strings and arrays better,
Don't ever write while (i < strlen(str))
Don't use strcat() for adding a single character, you apparently did overwrite the '\0' there. Furthermore, don't ever use strcat() for concatenating more than to pieces of a string.
Also notable,
You memset() after calloc() which already initialized to 0. That means that you are enforcing something that is not necessary, and trying it twice as if it failed the first time which I can guarantee it didn't.
In fact, since you have used calloc() and all bytes pointed to by tmp are 0 then strlen(tmp) will return 0, thus your memset() is equivalent to
tmp[0] = '\0';
and you REALLY don't need initialize tmp except when you finally copy the actual bytes from str.
I always advice against calloc() for strings, because
You don't really need to initialize something twice.
You should be sure your code does take the terminating '\0' into account and not simply assume that it's there because you calloc()ed. That is a bug that you just hide with calloc() but it shows up at some point.
Try this and see if you can understand the reasons for my changes
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int main(void)
{
int whitespace;
int length;
char *str = "ar bitrary whitespace";
char *tmp;
whitespace = 0;
for (length = 0; str[length] != '\0'; ++length) {
if (isspace(str[length]) != 0) {
whitespace++;
}
}
tmp = malloc(length - whitespace + 1);
if (tmp == NULL)
return -1;
for (int i = 0, j = 0; str[i] != '\0'; ++i) {
if (isspace(str[i]) != 0)
continue;
tmp[j++] = str[i];
}
tmp[length - whitespace] = '\0';
printf("new string is: %s\n",tmp);
free(tmp);
return 0;
}
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()).
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) {
The program runs fine except for the last free, which results in the program freezing.
When I comment out the last 'free' it runs fine.
The program gets all substrings from a string and returns it.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char** getPrefixes(char* invoer);
int main()
{
char buffer[100];
char *input;
char **prefixes;
int counter = 0;
puts("Give string.");
fgets(buffer, 99, stdin);
fflush(stdin);
if (buffer[strlen(buffer) - 1] == '\n')
buffer[strlen(buffer) - 1] = '\0';
input= (char*)malloc(strlen(buffer) + 1);
if (input == NULL)
{
puts("Error allocating memory.");
return;
}
strcpy(input, buffer);
prefixes = (char**) getPrefixes(input);
for (counter = strlen(input); counter > 0; counter--)
{
puts(prefixes[counter]);
free(prefixes[counter]);
}
free(input);
free(prefixes);
}
char** getPrefixes(char* input)
{
char** prefixes;
int counter;
prefixes = malloc(strlen(input) * sizeof(char*));
if (prefixes == NULL)
{
puts("ELM.");
return NULL;
}
for (counter= strlen(input); counter> 0; counter--)
{
prefixes[counter] = (char*)malloc(counter + 1);
strcpy(prefixes[counter], input);
input++;
}
return prefixes;
}
Thanks in advance!
The reason for your program freezing is simple: undefined behaviour + invalid return values: _Your main function returns void, not an int: add return 0 ASAP! If you type in echo $? in your console after executing your compiled binary, you should see a number other than 0. This is the program's exit code. anything other than 0 means trouble. if the main did not return an int, it's bad news.
Next:
The undefined behaviour occurs in a couple of places, for example right here:
prefixes = malloc(strlen(input) * sizeof(char*));
//allocate strlen(input) pointers, if input is 10 long => valid indexes == 0-9
for (counter= strlen(input); counter> 0; teller--)
{//teller doesn't exist, so I assume you meant "counter--"
prefixes[teller] = (char*)malloc(counter + 1);//first call prefixes[10] ==> out of bounds
strcpy(prefixes[counter], input);//risky, no zero-termination... use calloc + strncpy
input++;
}
Then, when free-ing the memory, you're not freeing the pointer # offset 0, so the free(prefixes) call is invalid:
for (counter = strlen(input); counter > 0; counter--)
{//again 10 --> valid offsets are 9 -> 0
puts(prefixes[counter]);
free(prefixes[counter]);
}
free(prefixes);//wrong
Again, valid indexes are 0 and up, your condition in the loop (counter > 0) means that the loop breaks whenever counter is 0. You, at no point, are freeing the first pointer in the array, the one at index/offstet 0.
Write your loops like everyone would:
for (int i=0, size_t len = strlen(input); i<len; ++i)
{
printf("%d\n", i);//prints 0-9... 10 lines, all valid indexes
}
Change your loops, and make sure you're only using the valid offsets and you _should be good to go. using strncpy, you can still get the same result as before:
for (int i=0;i<len;++i)
{
//or malloc(i+2), char is guaranteed to be 1
//I tend to use `calloc` to set all chars to 0 already, and ensure zero-termination
prefixes[i] = malloc((i+2)*sizeof(*prefixes[i]));
strncpy(prefixes[i], input, i+1);//max 1 - 10 chars are copied
}
If we apply this to your code, and re-write it like so:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char** getPrefixes(char* input);
int main( void )
{
char *input;
char **prefixes;
int counter, i;
input= calloc(50,1);
if (input == NULL)
{
puts("Error allocating memory.");
return;
}
strcpy(input, "teststring");
prefixes = getPrefixes(input);
counter = strlen(input);
for (i=0; i<counter;++i)
{
puts(prefixes[i]);
free(prefixes[i]);
}
free(input);
free(prefixes);
return 0;
}
char** getPrefixes(char* input)
{
int i, counter = strlen(input);
char** prefixes = malloc(counter * sizeof *prefixes);
if (prefixes == NULL)
{
puts("ELM.");
return NULL;
}
for (i=0; i<counter; ++i)
{
prefixes[i] = calloc(i + 2,sizeof *prefixes[i]);
strncpy(prefixes[i], input, i+1);
}
return prefixes;
}
The output we get is:
t
te
tes
test
tests
testst
teststr
teststri
teststrin
teststring
As you can see for yourself
on this codepad
allocating memory for pointer to pointer:
char** cArray = (char**)malloc(N*sizeof(char*));
for(i=0;i<N;i++)
cArray[i] = (char*)malloc(M*sizeof(char));
De-allocating memory - in reverse order:
for(i=0;i<N;i++)
free(cArray[i]);
free(cArray)
I hope this gives you a little insight on what's wrong.
you are calling strcpy with prefixes[counter] as destination. However, you've only allocated 4/8 bytes per prefixes[counter] depending on the size of (char*)
When you call strcpy you're copying all of input all the way to the end requiring strlen(input)! space
Doing this will corrupt the heap which might explain why the program is freezing.