How do I constantly get user input (strings) until enter is pressed in C just like string class in C++?
I don't know the input size so I can't declare a variable of fixed size or even I can't allocate memory dynamically using malloc() or calloc().
Is there any way to implement this as a separate function?
As H2CO3 said, you should allocate a buffer with malloc(), then resize it with realloc() whenever it fills up. Like this:
size_t bufsize = 256;
size_t buf_used = 0;
int c;
char *buf = malloc(bufsize);
if (buf == NULL) { /* error handling here */ }
while ((c = fgetc(stdin)) != EOF) {
if (c == '\n') break;
if (buf_used == bufsize-1) {
bufsize *= 2;
buf = realloc(buf, bufsize);
if (buf == NULL) { /* error handling here */ }
}
buf[buf_used++] = c;
}
buf[buf_used] = '\0';
Use exponential storage expansion:
char *read_a_line(void)
{
size_t alloc_size = LINE_MAX;
size_t len = 0;
char *buf = malloc(LINE_MAX); // should be good for most, euh, *lines*...
if (!buf)
abort();
int c;
while ((c = fgetc(stdin)) != '\n' && c != EOF) {
if (len >= alloc_size) {
alloc_size <<= 1;
char *tmp = realloc(buf, alloc_size);
if (!tmp)
abort(); // or whatever
buf = tmp;
}
buf[len++] = c;
}
if (len >= alloc_size) {
alloc_size++;
char *tmp = realloc(buf, alloc_size);
if (!tmp)
abort(); // or whatever
buf = tmp;
}
buf[len] = 0;
return buf;
}
In C, you have little choice: If you want to input a string of unbounded length, have to use allocations in a loop. Whether you use realloc() or a linked list of buffers, it comes down to reading (usually through fgets()), reading some more, and so on until the buffer you've just read contains a \n.
Then, depending on the method, you either already have a contiguous buffer (the realloc method) or just need to concatenate them all (the linked list method). Then you can return.
If you're lucky, your platform comes with the extension function getline() that does the realloc method for you. If not, you'll have to write it yourself.
Related
I've been trying to figure out what the problem is for hours and can't get it right. Here is the code, which is of course a lot longer but I've reduced it to the problem itself.
#define BUFFER_SIZE 60
char *str;
void readText() {
char read_char;
int i = 0;
str = (char *) calloc(BUFFER_SIZE, sizeof(char));
while ((read_char = getchar()) != EOF) { /* user hits ctrl+d */
*(str+i) = read_char;
i++;
if (i % BUFFER_SIZE == 0) {
str = (char *) realloc(str, BUFFER_SIZE * sizeof(char));
}
}
textSize = i;
/* Here I print the text... same error printing or not printing */
free(str);
}
}
I only get the error when the input text exceeds the buffer size.
(edited:
if (i % BUFFER_SIZE == 0) so it makes it every time it get to 60, but the error is the same
)
Thanks
That's because you are reallocating with the same size, you need to use the new size when the string grows up:
while ((read_char = getchar()) != EOF) { /* user hits ctrl+d */
if (i >= BUFFER_SIZE) {
str = realloc(str, i);
}
*(str+i) = read_char;
i++;
}
Also, it seems that you forget to set the trailing NUL, you need it in order to build a valid (printable) string, switch to
if (i >= BUFFER_SIZE - 1) {
and
str[i] = '\0';
after the while loop.
Finally, prefer
size_t i = 0; // or better yet size_t len = 0;
over
int i = 0;
to pass the size to realloc and friends.
I wrote this code, but inserts garbage in the start of string:
void append(char *s, char c) {
int len = strlen(s);
s[len] = c;
s[len + 1] = '\0';
}
int main(void) {
char c, *s;
int i = 0;
s = malloc(sizeof(char));
while ((c = getchar()) != '\n') {
i++;
s = realloc(s, i * sizeof(char));
append(s, c);
}
printf("\n%s",s);
}
How can I do it?
There are multiple problems in your code:
you iterate until you read a newline ('\n') from the standard input stream. This will cause an endless loop if the end of file occurs before you read a newline, which would happen if you redirect standard input from an empty file.
c should be defined as int so you can test for EOF properly.
s should be null terminated at all times, you must set the first byte to '\0' after malloc() as this function does not initialize the memory it allocates.
i should be initialized to 1 so the first realloc() extends the array by 1 etc. As coded, your array is one byte too short to accommodate the extra character.
you should check for memory allocation failure.
for good style, you should free the allocated memory before exiting the program
main() should return an int, preferably 0 for success.
Here is a corrected version:
#include <stdio.h>
#include <stdlib.h>
/* append a character to a string, assuming s points to an array with enough space */
void append(char *s, char c) {
size_t len = strlen(s);
s[len] = c;
s[len + 1] = '\0';
}
int main(void) {
int c;
char *s;
size_t i = 1;
s = malloc(i * sizeof(char));
if (s == NULL) {
printf("memory allocation failure\n");
return 1;
}
*s = '\0';
while ((c = getchar()) != EOF && c != '\n') {
i++;
s = realloc(s, i * sizeof(char));
if (s == NULL) {
printf("memory allocation failure\n");
return 1;
}
append(s, c);
}
printf("%s\n", s);
free(s);
return 0;
}
when you call strlen it searches for a '\0' char to end the string. You don't have this char inside your string to the behavior of strlen is unpredictable.
Your append function is acually good.
Also, a minor thing, you need to add return 0; to your main function. And i should start from 1 instead if 0.
Here is how it should look:
int main(void){
char *s;
size_t i = 1;
s = malloc (i * sizeof(char));//Just for fun. The i is not needed.
if(s == NULL) {
fprintf(stderr, "Coul'd not allocate enough memory");
return 1;
}
s[0] = '\0';
for(char c = getchar(); c != '\n' && c != EOF; c = getchar()) {//it is not needed in this case to store the result as an int.
i++;
s = realloc (s,i * sizeof(char) );
if(s == NULL) {
fprintf(stderr, "Coul'd not allocate enough memory");
return 1;
}
append (s,c);
}
printf("%s\n",s);
return 0;
}
Thanks for the comments that helped me improve the code (and for my english). I am not perfect :)
The inner realloc needs to allocate one element more (for the trailing \0) and you have to initialize s[0] = '\0' before starting the loop.
Btw, you can replace your append by strcat() or write it like
size_t i = 0;
s = malloc(1);
/* TODO: check for s != NULL */
while ((c = getchar()) != '\n') {
s[i] = c;
i++;
s = realloc(s, i + 1);
/* TODO: check for s != NULL */
}
s[i] = '\0';
I wrote this simple readline function, it can return each line length but it doesn't return a pointer to the allocated buffer. Another issue is the last line ignored(it doesn't return it):
FILE *passFile = NULL;
char *current = NULL;
size_t len = 0;
passFile = fopen("pass.txt", "r");
while(readline(passFile, ¤t, &len) != -1) {
printf("%s\n", current); // SEGMENTAION FAULT
printf("%d\n", len);
free(current);
current = NULL;
}
ssize_t
readline(FILE *file, char **bufPtr, size_t *len)
{
char c, *buf = NULL;
size_t n = 0;
buf = (char*)malloc(sizeof(char));
while((c = fgetc(file)) != '\n' && (c != EOF)) {
buf[n] = c;
++n;
buf = realloc(buf, n + 1);
}
buf[n] = '\0';
*bufPtr = buf;
*len = n;
if(c == EOF) // reach end of file
return -1;
return 0;
}
Your readline() function is not returning a pointer to allocated memory. In your call, current is never set, so the pointer is invalid and you get the error.
In C, functions are "call by value". Inside readline(), bufPtr is a copy of whatever was passed to readline(). Assigning to bufPtr merely overwrites the local copy and does not return a value that the calling code can see.
In pseudocode:
TYPE a;
define function foo(TYPE x)
{
x = new_value;
}
foo(a); // does not change a
This only changes the local copy of x and does not return a value. You change it to use a pointer... the function still gets a copy, but now it's a copy of a pointer, and it can use that pointer value to find the original variable. In pseudocode:
TYPE a;
define function foo(TYPE *px)
{
*px = new_value;
}
foo(&a); // does change a
Now, to change your function:
ssize_t
readline(FILE *file, char **pbufPtr, size_t *len)
{
// ...deleted...
buf[n] = '\0';
*pbufPtr = buf;
// ...deleted...
}
And you call it like so:
while(readline(passFile, ¤t, &len) != -1)
P.S. It is not a good idea to call realloc() the way you do here. It's potentially a very slow function, and for an input string of 65 characters you will call it 65 times. It would be better to use an internal buffer for the initial file input, then use malloc() to allocate a string that is just the right size and copy the string into the buffer. If the string is too long to fit in the internal buffer at once, use malloc() to get a big-enough place to copy out the part of the string you have in the internal buffer, then continue using the internal buffer to copy more of the string, and then call realloc() as needed. Basically I'm suggesting you have an internal buffer of size N, and copy the string in chunks of N characters at a time, thus minimizing the number of calls to realloc() while still allowing arbitrary-length input strings.
EDIT: Your last-line problem is that you return -1 when you hit end of file, even though there is a line to return.
Change your code so that you return -1 only if c == EOF and n == 0, so a final line that ends with EOF will be correctly returned.
You should also make readline() use the feof() function to check if file is at end-of-file, and if so, return -1 without calling malloc().
Basically, when you return -1, you don't want to call malloc(), and when you did call malloc() and copy data into it, you don't want to return -1! -1 should mean "you got nothing because we hit end of file". If you got something before we hit end of file, that's not -1, that is 0. Then the next call to readline() after that will return -1.
In your readline function you pass current by value. So if you change bufPtr inside your function, it doesn't change value of current outside. If you want to change value of current pass it by reference: ¤t and change readline() parameter to char **bufPTR.
You could pass current the way you did if you wanted to change something it points to, but you want to change where it points in first place.
replace your readlinefunction with this
char* readline(FILE *file, size_t *len)
{
char c, *buf = NULL;
size_t n = 0;
buf = (char*)malloc(sizeof(char));
while((c = fgetc(file)) != '\n' && (c != EOF)) {
buf[n] = c;
++n;
buf = realloc(buf, n + 1);
}
buf[n] = '\0';
bufPtr = buf;
*len = n;
if(c == EOF) // reach end of file
return NULL;
return buf;
}
and then in main replace this line while(readline(passFile, current, &len) != -1) with this while((current = readline(passFile, &len) != NULL)
Now it works:
ssize_t
readline(FILE *file, char **bufPtr, size_t *len)
{
if(feof(file)) // reach end of file
return -1;
char c, *buf = NULL;
size_t n = 0, portion = CHUNK;
buf = (char*)malloc(sizeof(char) * CHUNK);
while((c = fgetc(file)) != '\n' && (c != EOF)) {
buf[n] = c;
++n;
if(n == portion) {
buf = realloc(buf, CHUNK + n);
portion += n;
}
}
buf[n] = '\0';
*bufPtr = buf;
*len = n;
return 0;
}
I'm using this function to read, char by char, a text file or a stdin input
void readLine(FILE *stream, char **string) {
char c;
int counter = 0;
do {
c = fgetc(stream);
string[0] = (char *) realloc (string[0], (counter+1) * sizeof(char));
string[0][counter++] = c;
} while(c != ENTER && !feof(stream));
string[counter-1] = '\0';
}
But when I call it, my program crashed and I really don't know why, because I don't forget the 0-terminator and I'm convinced that I stored correctly the char sequence. I've verified the string length, but it appears alright.
This is an error:
do {
c = fgetc(stream);
// What happens here?!?
} while(c != ENTER && !feof(stream));
"What happens here" is that you add c to string before you've checked for EOF, whoops.
This is very ungood:
string[0] = (char *) realloc (string[0], (counter+1) * sizeof(char));
in a loop. realloc is a potentially expensive call and you do it for every byte of input! It is also a silly and confusing interface to ask for a pointer parameter that has (apparently) not been allocated anything -- passing in the pointer usually indicates that is already done. What if string were a static array? Instead, allocate in chunks and return a pointer:
char *readLine (FILE *stream) {
// A whole 4 kB!
int chunksz = 4096;
int counter = 0;
char *buffer = malloc(chunksz);
char *test;
int c;
if (!buffer) return NULL;
while (c = fgetc(stream) && c != ENTER && c != EOF) {
buffer[counter++] = (char)c;
if (counter == chunksz) {
chunksz *= 2;
test = realloc(buffer, chunksz);
// Abort on out-of-memory.
if (!test) {
free(buffer);
return NULL;
} else buffer = test;
}
}
// Now null terminate and resize.
buffer[counter] = '\0';
realloc(buffer, counter + 1);
return buffer;
}
That is a standard "power of 2" allocation scheme (it doubles). If you really want to submit a pointer, pre-allocate it and also submit a "max length" parameter:
void *readLine (FILE *stream, char *buffer, int max) {
int counter = 0;
int c;
while (
c = fgetc(stream)
&& c != ENTER
&& c != EOF
&& counter < max - 1
) buffer[counter++] = (char)c;
// Now null terminate.
buffer[counter] = '\0';
}
There are a few issues in this code:
fgetc() returns int.
Don't cast the return value of malloc() and friends, in C.
Avoid using sizeof (char), it's just a very clumsy way of writing 1, so multiplication by it is very redundant.
Normally, buffers are grown more than 1 char at a time, realloc() can be expensive.
string[0] would be more clearly written as *string, since it's not an array but just a pointer to a pointer.
Your logic around end of file means it will store the truncated version of EOF, not very nice.
Change this line
string[counter-1] = '\0';
to
string[0][counter-1] = '\0';
You want to terminate string stored at string[0].
In C i can use the char *fgets(char *s, int size, FILE *stream) function to read user input from stdin. But the size of the user input is limited to size.
How can i read user input of variable size?
In C you are responsible for your buffers, and responsible for their size. So you can not have some dynamic buffer ready for you.
So the only solution is to use a loop (either of fgets or fgetc - depends on your processing and on your stop condition)
If you go beyond C to C++, you will find that you can accept std::string objects of variable sizes (there you need to deal with word and/or line termination instead - and loop again)
This function reads from standard input until end-of-file is encountered, and returns the number of characters read. It should be fairly easy to modify it to read exactly one line, or alike.
ssize_t read_from_stdin(char **s)
{
char buf[1024];
char *p;
char *tmp;
ssize_t total;
size_t len;
size_t allocsize;
if (s == NULL) {
return -1;
}
total = 0;
allocsize = 1024;
p = malloc(allocsize);
if (p == NULL) {
*s = NULL;
return -1;
}
while(fgets(buf, sizeof(buf), stdin) != NULL) {
len = strlen(buf);
if (total + len >= allocsize) {
allocsize <<= 1;
tmp = realloc(p, allocsize);
if (tmp == NULL) {
free(p);
*s = NULL;
return -1;
}
p = tmp;
}
memcpy(p + total, buf, len);
total += len;
}
p[total] = 0;
*s = p;
return total;
}