Emulating C++ string input in C

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
7
down vote

favorite
4












I did the following exercise:




Write a C program that does the equivalent of C++ string s; cin>>s;; that is, define an input operation that reads an arbitrarily long sequence of whitespace-terminated characters into a zero terminated array of chars.




I wonder if it's good code. What could be improved?



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* signs;
size_t size;
size_t capacity;
;

void String_allocate_space(char **c, size_t *capacity)

if (*capacity == 0) // allocate the first time
*capacity = 1;
*c = malloc(sizeof(**c) * ((*capacity)));

else
*capacity *= 2; // double the new capacity
*c = realloc(*c, sizeof(**c) * (*capacity));

if (*c == NULL)
exit(-1);


void add_character(struct String* string, int ch)

if (string->size == string->capacity) // if current letter sz = capacity
String_allocate_space(&string->signs, &string->capacity);

string->signs[string->size++] = ch; // append the sign in the array


void String_read(struct String* string)

int ch = ' ';
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');


void String_print(struct String* string)

printf("%s", string->signs);


void String_free(struct String* string)

int i = 0;
for (i = 0; i < string->capacity; ++i)
free(string[i].signs);
string[i].signs = NULL;



int main()

struct String string = 0 ;

String_read(&string);
String_print(&string);
String_free(&string);

getchar();
return 0;







share|improve this question





















  • This approach consumes the trailing delimiter. Its value is lost. Is that truly intended?
    – chux
    Jun 26 at 11:33










  • @chux That’s pretty standard behaviour for formatted input in C and C++.
    – Konrad Rudolph
    Jun 26 at 11:36










  • @KonradRudolph In C, it is not. scanf("%s", buf) does not consume trailing white-space after the populating buf. White-space is detected, yet returned to stdin. Similar for scanf("%d", &i) and others.
    – chux
    Jun 26 at 11:46











  • @chux Fair point. I misremembered.
    – Konrad Rudolph
    Jun 26 at 12:24
















up vote
7
down vote

favorite
4












I did the following exercise:




Write a C program that does the equivalent of C++ string s; cin>>s;; that is, define an input operation that reads an arbitrarily long sequence of whitespace-terminated characters into a zero terminated array of chars.




I wonder if it's good code. What could be improved?



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* signs;
size_t size;
size_t capacity;
;

void String_allocate_space(char **c, size_t *capacity)

if (*capacity == 0) // allocate the first time
*capacity = 1;
*c = malloc(sizeof(**c) * ((*capacity)));

else
*capacity *= 2; // double the new capacity
*c = realloc(*c, sizeof(**c) * (*capacity));

if (*c == NULL)
exit(-1);


void add_character(struct String* string, int ch)

if (string->size == string->capacity) // if current letter sz = capacity
String_allocate_space(&string->signs, &string->capacity);

string->signs[string->size++] = ch; // append the sign in the array


void String_read(struct String* string)

int ch = ' ';
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');


void String_print(struct String* string)

printf("%s", string->signs);


void String_free(struct String* string)

int i = 0;
for (i = 0; i < string->capacity; ++i)
free(string[i].signs);
string[i].signs = NULL;



int main()

struct String string = 0 ;

String_read(&string);
String_print(&string);
String_free(&string);

getchar();
return 0;







share|improve this question





















  • This approach consumes the trailing delimiter. Its value is lost. Is that truly intended?
    – chux
    Jun 26 at 11:33










  • @chux That’s pretty standard behaviour for formatted input in C and C++.
    – Konrad Rudolph
    Jun 26 at 11:36










  • @KonradRudolph In C, it is not. scanf("%s", buf) does not consume trailing white-space after the populating buf. White-space is detected, yet returned to stdin. Similar for scanf("%d", &i) and others.
    – chux
    Jun 26 at 11:46











  • @chux Fair point. I misremembered.
    – Konrad Rudolph
    Jun 26 at 12:24












up vote
7
down vote

favorite
4









up vote
7
down vote

favorite
4






4





I did the following exercise:




Write a C program that does the equivalent of C++ string s; cin>>s;; that is, define an input operation that reads an arbitrarily long sequence of whitespace-terminated characters into a zero terminated array of chars.




I wonder if it's good code. What could be improved?



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* signs;
size_t size;
size_t capacity;
;

void String_allocate_space(char **c, size_t *capacity)

if (*capacity == 0) // allocate the first time
*capacity = 1;
*c = malloc(sizeof(**c) * ((*capacity)));

else
*capacity *= 2; // double the new capacity
*c = realloc(*c, sizeof(**c) * (*capacity));

if (*c == NULL)
exit(-1);


void add_character(struct String* string, int ch)

if (string->size == string->capacity) // if current letter sz = capacity
String_allocate_space(&string->signs, &string->capacity);

string->signs[string->size++] = ch; // append the sign in the array


void String_read(struct String* string)

int ch = ' ';
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');


void String_print(struct String* string)

printf("%s", string->signs);


void String_free(struct String* string)

int i = 0;
for (i = 0; i < string->capacity; ++i)
free(string[i].signs);
string[i].signs = NULL;



int main()

struct String string = 0 ;

String_read(&string);
String_print(&string);
String_free(&string);

getchar();
return 0;







share|improve this question













I did the following exercise:




Write a C program that does the equivalent of C++ string s; cin>>s;; that is, define an input operation that reads an arbitrarily long sequence of whitespace-terminated characters into a zero terminated array of chars.




I wonder if it's good code. What could be improved?



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* signs;
size_t size;
size_t capacity;
;

void String_allocate_space(char **c, size_t *capacity)

if (*capacity == 0) // allocate the first time
*capacity = 1;
*c = malloc(sizeof(**c) * ((*capacity)));

else
*capacity *= 2; // double the new capacity
*c = realloc(*c, sizeof(**c) * (*capacity));

if (*c == NULL)
exit(-1);


void add_character(struct String* string, int ch)

if (string->size == string->capacity) // if current letter sz = capacity
String_allocate_space(&string->signs, &string->capacity);

string->signs[string->size++] = ch; // append the sign in the array


void String_read(struct String* string)

int ch = ' ';
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');


void String_print(struct String* string)

printf("%s", string->signs);


void String_free(struct String* string)

int i = 0;
for (i = 0; i < string->capacity; ++i)
free(string[i].signs);
string[i].signs = NULL;



int main()

struct String string = 0 ;

String_read(&string);
String_print(&string);
String_free(&string);

getchar();
return 0;









share|improve this question












share|improve this question




share|improve this question








edited Jun 25 at 17:05









Toby Speight

17.2k13487




17.2k13487









asked Jun 25 at 16:21









Sandro4912

467119




467119











  • This approach consumes the trailing delimiter. Its value is lost. Is that truly intended?
    – chux
    Jun 26 at 11:33










  • @chux That’s pretty standard behaviour for formatted input in C and C++.
    – Konrad Rudolph
    Jun 26 at 11:36










  • @KonradRudolph In C, it is not. scanf("%s", buf) does not consume trailing white-space after the populating buf. White-space is detected, yet returned to stdin. Similar for scanf("%d", &i) and others.
    – chux
    Jun 26 at 11:46











  • @chux Fair point. I misremembered.
    – Konrad Rudolph
    Jun 26 at 12:24
















  • This approach consumes the trailing delimiter. Its value is lost. Is that truly intended?
    – chux
    Jun 26 at 11:33










  • @chux That’s pretty standard behaviour for formatted input in C and C++.
    – Konrad Rudolph
    Jun 26 at 11:36










  • @KonradRudolph In C, it is not. scanf("%s", buf) does not consume trailing white-space after the populating buf. White-space is detected, yet returned to stdin. Similar for scanf("%d", &i) and others.
    – chux
    Jun 26 at 11:46











  • @chux Fair point. I misremembered.
    – Konrad Rudolph
    Jun 26 at 12:24















This approach consumes the trailing delimiter. Its value is lost. Is that truly intended?
– chux
Jun 26 at 11:33




This approach consumes the trailing delimiter. Its value is lost. Is that truly intended?
– chux
Jun 26 at 11:33












@chux That’s pretty standard behaviour for formatted input in C and C++.
– Konrad Rudolph
Jun 26 at 11:36




@chux That’s pretty standard behaviour for formatted input in C and C++.
– Konrad Rudolph
Jun 26 at 11:36












@KonradRudolph In C, it is not. scanf("%s", buf) does not consume trailing white-space after the populating buf. White-space is detected, yet returned to stdin. Similar for scanf("%d", &i) and others.
– chux
Jun 26 at 11:46





@KonradRudolph In C, it is not. scanf("%s", buf) does not consume trailing white-space after the populating buf. White-space is detected, yet returned to stdin. Similar for scanf("%d", &i) and others.
– chux
Jun 26 at 11:46













@chux Fair point. I misremembered.
– Konrad Rudolph
Jun 26 at 12:24




@chux Fair point. I misremembered.
– Konrad Rudolph
Jun 26 at 12:24










2 Answers
2






active

oldest

votes

















up vote
14
down vote



accepted










Don't do this:



*c = realloc(*c, sizeof(**c) * (*capacity));


Once you have error handling that's more sophisticated than exit(1), this will become a liability. You need a temporary:



char *tmp = realloc(*c, new_capacity);
if (!tmp)
/* error handling - c is still valid */
/* ... */

*c = tmp;
*capacity = new_capacity;



If you always initialize the data pointer to start as a null pointer, you don't need to use malloc() instead of realloc(). String_allocate_space would be easier to write if it accepts a pointer to a struct String; that makes it closer to the object-oriented version:



void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 16;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;



I've also incorporated a change above to start with a larger initial size (16) instead of 1. That lets us skip the first 4 reallocations for free.




The read() method has a flaw that will become apparent when you try to read another value into a string - unlike std::string, reading with >> will append to it, instead of replacing it. We need to reset size at the beginning:



void String_read(struct String* string)

string->size = 0;
int ch;
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');



Also, the logic is slightly wrong - we want to finish when we see a space, rather than any non-alpha (which could be digits or punctuation characters). (Well done for remembering that getch() returns int rather than char - that's one common error avoided).



void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);

add_character(string, '');




When we free the string, we don't need a loop. Instead, we have a single free(). It's a good idea to reset the size and capacity so that the string object is consistent - it can be used again and/or freed again without harm:



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;



This is an important concept in object-oriented programming - objects have invariants that they guarantee are true at the start and end of every (public) operation. In this case, the invariants are that




  • data points to valid storage of at least capacity if capacity > 0 and is a null pointer otherwise.


  • size is not greater than capacity.


We can improve the printing so that it outputs any embedded NUL characters, just like C++ strings do:



void String_print(struct String* string)

if (fwrite(string->data, 1, string->size, stdout) != string->size)
exit(1); /* TODO: improve error reporting */




Note that this will now print the trailing NUL we added. We no longer need that to mark the end of string, so we can remove that line.




Modified code



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* data;
size_t size;
size_t capacity;
;

void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 1;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;


void add_character(struct String* string, char ch)

if (string->size == string->capacity) // if current letter exceeds capacity
String_allocate_space(string);

string->data[string->size++] = ch; // append it


void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);



void String_print(struct String *restrict string, FILE *restrict stream)

if (fwrite(string->data, 1, string->size, stream) != string->size)
exit(1); /* TODO: improve error reporting */



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;


int main()

struct String string;

String_init(&string);
String_read(&string);
String_print(&string, stdout);
String_free(&string);






share|improve this answer























  • thanks for the in deep review. i have to admit i didn't even expected to read much comments to this code because i considered the code to easy and short :-) I was quite wrong... One question i have hwat would be an better error handling? and why exit(1) not exit(-1)
    – Sandro4912
    Jun 25 at 18:34










  • One nit to pick. I would change "if (!tmp)" to "if (tmp == NULL)", because tmp is not a logical (true/false) condition, it's a comparison to a special value, the NULL pointer. FALSE and NULL just happen to be implemented as 0 in C, but that's not necessarily universally true.
    – jamesqf
    Jun 25 at 20:33






  • 3




    @james/hoffmale: It's a standard C (and C++) idiom to test the validity of a pointer by leaving the comparison to 0 implicit. Remember that a literal 0 always means a null pointer (whether or not the machine uses all-bits-zero for its representation). NULL doesn't "just happen" to be implemented as 0 - the Standard insists that it must be 0, even on hardware where the zero address is valid. In those cases, the implementation is required to evaluate a pointer holding the zero address as a "true" value.
    – Toby Speight
    Jun 26 at 7:35







  • 1




    Further reading: see the answers to Does Standard define null pointer constant to have all bits set to zero?, Why is address zero used for the null pointer? and Can I use if (pointer) instead of if (pointer != NULL)?.
    – Toby Speight
    Jun 26 at 7:42






  • 1




    Detail: "0 always means a null pointer" is amiss. 0 is always a constant int with value 0. Comparing that int to a pointer p, converts the int 0 to a null pointer. IAC, if (!tmp) is OK, yet as a minor point, I prefer if (tmp == NULL) as negations tend to be less clear than positive assertions - don't you not think is won't be otherwise ;-)? (Good review)
    – chux
    Jun 26 at 11:41

















up vote
8
down vote













Since @TobySpeight has already posted a wonderful answer, I'm not going to repeat what he has already posted.



Just some small additional notes:



  • String_allocate_space allocates 1 chars worth of memory if the capacity is 0. This is barely enough to hold the terminating '' character, but nothing more. Maybe increase the default minimal allocation size a bit to be meaningful?


  • Also, regarding naming: If I see a function called String_free, I'd expect a function String_alloc that allocates and creates the String object in a well-defined state. (And consequently, String_free should then deallocate that String correctly.)


Also, this might just me, but from the task description I'd expect the solution to be a char *read_input(void) function. While the String "class" is nice, it seems like a bit of over-engineering for the task at hand.



For comparison, look at this solution:



#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#define INITIAL_INPUT_CAPACITY 4

char *read_input(void)
size_t capacity = INITIAL_INPUT_CAPACITY;
size_t size = 0;
char *str = malloc(capacity * sizeof(char));
int input;

while((input = getc(stdin)) != EOF)
if(isspace(input)) break;

str[size++] = (char)input;

if(size == capacity)
capacity *= 2;
char *temp = realloc(str, capacity * sizeof(char));

if(temp == NULL)
exit(-1);


str = temp;



str[size++] = '';

return str;


int main(void)
char *input = read_input();
puts(input);
free(input);

getchar();
return 0;






share|improve this answer























  • I was continuing my answer as you wrote this. I didn't think of the better initial capacity, though.
    – Toby Speight
    Jun 25 at 16:57










  • i think this code reflects more the ximplicity of c. since i do mostly c++ i think i tend to overengineer "easy" tasks
    – Sandro4912
    Jun 25 at 18:35










  • @Sandro4912: I don't think this is about "simplicity of C". C programs can very easily become very complex, even more complex than C++ with classes and templates. This is about using the right tool for the job, adhering to general language idioms and not doing more than what is needed.
    – hoffmale
    Jun 25 at 18:48










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197218%2femulating-c-string-input-in-c%23new-answer', 'question_page');

);

Post as a guest






























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
14
down vote



accepted










Don't do this:



*c = realloc(*c, sizeof(**c) * (*capacity));


Once you have error handling that's more sophisticated than exit(1), this will become a liability. You need a temporary:



char *tmp = realloc(*c, new_capacity);
if (!tmp)
/* error handling - c is still valid */
/* ... */

*c = tmp;
*capacity = new_capacity;



If you always initialize the data pointer to start as a null pointer, you don't need to use malloc() instead of realloc(). String_allocate_space would be easier to write if it accepts a pointer to a struct String; that makes it closer to the object-oriented version:



void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 16;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;



I've also incorporated a change above to start with a larger initial size (16) instead of 1. That lets us skip the first 4 reallocations for free.




The read() method has a flaw that will become apparent when you try to read another value into a string - unlike std::string, reading with >> will append to it, instead of replacing it. We need to reset size at the beginning:



void String_read(struct String* string)

string->size = 0;
int ch;
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');



Also, the logic is slightly wrong - we want to finish when we see a space, rather than any non-alpha (which could be digits or punctuation characters). (Well done for remembering that getch() returns int rather than char - that's one common error avoided).



void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);

add_character(string, '');




When we free the string, we don't need a loop. Instead, we have a single free(). It's a good idea to reset the size and capacity so that the string object is consistent - it can be used again and/or freed again without harm:



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;



This is an important concept in object-oriented programming - objects have invariants that they guarantee are true at the start and end of every (public) operation. In this case, the invariants are that




  • data points to valid storage of at least capacity if capacity > 0 and is a null pointer otherwise.


  • size is not greater than capacity.


We can improve the printing so that it outputs any embedded NUL characters, just like C++ strings do:



void String_print(struct String* string)

if (fwrite(string->data, 1, string->size, stdout) != string->size)
exit(1); /* TODO: improve error reporting */




Note that this will now print the trailing NUL we added. We no longer need that to mark the end of string, so we can remove that line.




Modified code



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* data;
size_t size;
size_t capacity;
;

void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 1;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;


void add_character(struct String* string, char ch)

if (string->size == string->capacity) // if current letter exceeds capacity
String_allocate_space(string);

string->data[string->size++] = ch; // append it


void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);



void String_print(struct String *restrict string, FILE *restrict stream)

if (fwrite(string->data, 1, string->size, stream) != string->size)
exit(1); /* TODO: improve error reporting */



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;


int main()

struct String string;

String_init(&string);
String_read(&string);
String_print(&string, stdout);
String_free(&string);






share|improve this answer























  • thanks for the in deep review. i have to admit i didn't even expected to read much comments to this code because i considered the code to easy and short :-) I was quite wrong... One question i have hwat would be an better error handling? and why exit(1) not exit(-1)
    – Sandro4912
    Jun 25 at 18:34










  • One nit to pick. I would change "if (!tmp)" to "if (tmp == NULL)", because tmp is not a logical (true/false) condition, it's a comparison to a special value, the NULL pointer. FALSE and NULL just happen to be implemented as 0 in C, but that's not necessarily universally true.
    – jamesqf
    Jun 25 at 20:33






  • 3




    @james/hoffmale: It's a standard C (and C++) idiom to test the validity of a pointer by leaving the comparison to 0 implicit. Remember that a literal 0 always means a null pointer (whether or not the machine uses all-bits-zero for its representation). NULL doesn't "just happen" to be implemented as 0 - the Standard insists that it must be 0, even on hardware where the zero address is valid. In those cases, the implementation is required to evaluate a pointer holding the zero address as a "true" value.
    – Toby Speight
    Jun 26 at 7:35







  • 1




    Further reading: see the answers to Does Standard define null pointer constant to have all bits set to zero?, Why is address zero used for the null pointer? and Can I use if (pointer) instead of if (pointer != NULL)?.
    – Toby Speight
    Jun 26 at 7:42






  • 1




    Detail: "0 always means a null pointer" is amiss. 0 is always a constant int with value 0. Comparing that int to a pointer p, converts the int 0 to a null pointer. IAC, if (!tmp) is OK, yet as a minor point, I prefer if (tmp == NULL) as negations tend to be less clear than positive assertions - don't you not think is won't be otherwise ;-)? (Good review)
    – chux
    Jun 26 at 11:41














up vote
14
down vote



accepted










Don't do this:



*c = realloc(*c, sizeof(**c) * (*capacity));


Once you have error handling that's more sophisticated than exit(1), this will become a liability. You need a temporary:



char *tmp = realloc(*c, new_capacity);
if (!tmp)
/* error handling - c is still valid */
/* ... */

*c = tmp;
*capacity = new_capacity;



If you always initialize the data pointer to start as a null pointer, you don't need to use malloc() instead of realloc(). String_allocate_space would be easier to write if it accepts a pointer to a struct String; that makes it closer to the object-oriented version:



void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 16;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;



I've also incorporated a change above to start with a larger initial size (16) instead of 1. That lets us skip the first 4 reallocations for free.




The read() method has a flaw that will become apparent when you try to read another value into a string - unlike std::string, reading with >> will append to it, instead of replacing it. We need to reset size at the beginning:



void String_read(struct String* string)

string->size = 0;
int ch;
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');



Also, the logic is slightly wrong - we want to finish when we see a space, rather than any non-alpha (which could be digits or punctuation characters). (Well done for remembering that getch() returns int rather than char - that's one common error avoided).



void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);

add_character(string, '');




When we free the string, we don't need a loop. Instead, we have a single free(). It's a good idea to reset the size and capacity so that the string object is consistent - it can be used again and/or freed again without harm:



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;



This is an important concept in object-oriented programming - objects have invariants that they guarantee are true at the start and end of every (public) operation. In this case, the invariants are that




  • data points to valid storage of at least capacity if capacity > 0 and is a null pointer otherwise.


  • size is not greater than capacity.


We can improve the printing so that it outputs any embedded NUL characters, just like C++ strings do:



void String_print(struct String* string)

if (fwrite(string->data, 1, string->size, stdout) != string->size)
exit(1); /* TODO: improve error reporting */




Note that this will now print the trailing NUL we added. We no longer need that to mark the end of string, so we can remove that line.




Modified code



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* data;
size_t size;
size_t capacity;
;

void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 1;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;


void add_character(struct String* string, char ch)

if (string->size == string->capacity) // if current letter exceeds capacity
String_allocate_space(string);

string->data[string->size++] = ch; // append it


void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);



void String_print(struct String *restrict string, FILE *restrict stream)

if (fwrite(string->data, 1, string->size, stream) != string->size)
exit(1); /* TODO: improve error reporting */



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;


int main()

struct String string;

String_init(&string);
String_read(&string);
String_print(&string, stdout);
String_free(&string);






share|improve this answer























  • thanks for the in deep review. i have to admit i didn't even expected to read much comments to this code because i considered the code to easy and short :-) I was quite wrong... One question i have hwat would be an better error handling? and why exit(1) not exit(-1)
    – Sandro4912
    Jun 25 at 18:34










  • One nit to pick. I would change "if (!tmp)" to "if (tmp == NULL)", because tmp is not a logical (true/false) condition, it's a comparison to a special value, the NULL pointer. FALSE and NULL just happen to be implemented as 0 in C, but that's not necessarily universally true.
    – jamesqf
    Jun 25 at 20:33






  • 3




    @james/hoffmale: It's a standard C (and C++) idiom to test the validity of a pointer by leaving the comparison to 0 implicit. Remember that a literal 0 always means a null pointer (whether or not the machine uses all-bits-zero for its representation). NULL doesn't "just happen" to be implemented as 0 - the Standard insists that it must be 0, even on hardware where the zero address is valid. In those cases, the implementation is required to evaluate a pointer holding the zero address as a "true" value.
    – Toby Speight
    Jun 26 at 7:35







  • 1




    Further reading: see the answers to Does Standard define null pointer constant to have all bits set to zero?, Why is address zero used for the null pointer? and Can I use if (pointer) instead of if (pointer != NULL)?.
    – Toby Speight
    Jun 26 at 7:42






  • 1




    Detail: "0 always means a null pointer" is amiss. 0 is always a constant int with value 0. Comparing that int to a pointer p, converts the int 0 to a null pointer. IAC, if (!tmp) is OK, yet as a minor point, I prefer if (tmp == NULL) as negations tend to be less clear than positive assertions - don't you not think is won't be otherwise ;-)? (Good review)
    – chux
    Jun 26 at 11:41












up vote
14
down vote



accepted







up vote
14
down vote



accepted






Don't do this:



*c = realloc(*c, sizeof(**c) * (*capacity));


Once you have error handling that's more sophisticated than exit(1), this will become a liability. You need a temporary:



char *tmp = realloc(*c, new_capacity);
if (!tmp)
/* error handling - c is still valid */
/* ... */

*c = tmp;
*capacity = new_capacity;



If you always initialize the data pointer to start as a null pointer, you don't need to use malloc() instead of realloc(). String_allocate_space would be easier to write if it accepts a pointer to a struct String; that makes it closer to the object-oriented version:



void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 16;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;



I've also incorporated a change above to start with a larger initial size (16) instead of 1. That lets us skip the first 4 reallocations for free.




The read() method has a flaw that will become apparent when you try to read another value into a string - unlike std::string, reading with >> will append to it, instead of replacing it. We need to reset size at the beginning:



void String_read(struct String* string)

string->size = 0;
int ch;
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');



Also, the logic is slightly wrong - we want to finish when we see a space, rather than any non-alpha (which could be digits or punctuation characters). (Well done for remembering that getch() returns int rather than char - that's one common error avoided).



void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);

add_character(string, '');




When we free the string, we don't need a loop. Instead, we have a single free(). It's a good idea to reset the size and capacity so that the string object is consistent - it can be used again and/or freed again without harm:



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;



This is an important concept in object-oriented programming - objects have invariants that they guarantee are true at the start and end of every (public) operation. In this case, the invariants are that




  • data points to valid storage of at least capacity if capacity > 0 and is a null pointer otherwise.


  • size is not greater than capacity.


We can improve the printing so that it outputs any embedded NUL characters, just like C++ strings do:



void String_print(struct String* string)

if (fwrite(string->data, 1, string->size, stdout) != string->size)
exit(1); /* TODO: improve error reporting */




Note that this will now print the trailing NUL we added. We no longer need that to mark the end of string, so we can remove that line.




Modified code



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* data;
size_t size;
size_t capacity;
;

void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 1;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;


void add_character(struct String* string, char ch)

if (string->size == string->capacity) // if current letter exceeds capacity
String_allocate_space(string);

string->data[string->size++] = ch; // append it


void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);



void String_print(struct String *restrict string, FILE *restrict stream)

if (fwrite(string->data, 1, string->size, stream) != string->size)
exit(1); /* TODO: improve error reporting */



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;


int main()

struct String string;

String_init(&string);
String_read(&string);
String_print(&string, stdout);
String_free(&string);






share|improve this answer















Don't do this:



*c = realloc(*c, sizeof(**c) * (*capacity));


Once you have error handling that's more sophisticated than exit(1), this will become a liability. You need a temporary:



char *tmp = realloc(*c, new_capacity);
if (!tmp)
/* error handling - c is still valid */
/* ... */

*c = tmp;
*capacity = new_capacity;



If you always initialize the data pointer to start as a null pointer, you don't need to use malloc() instead of realloc(). String_allocate_space would be easier to write if it accepts a pointer to a struct String; that makes it closer to the object-oriented version:



void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 16;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;



I've also incorporated a change above to start with a larger initial size (16) instead of 1. That lets us skip the first 4 reallocations for free.




The read() method has a flaw that will become apparent when you try to read another value into a string - unlike std::string, reading with >> will append to it, instead of replacing it. We need to reset size at the beginning:



void String_read(struct String* string)

string->size = 0;
int ch;
while (ch = getc(stdin))

if (!isalpha(ch))
break;

add_character(string, ch);

add_character(string, '');



Also, the logic is slightly wrong - we want to finish when we see a space, rather than any non-alpha (which could be digits or punctuation characters). (Well done for remembering that getch() returns int rather than char - that's one common error avoided).



void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);

add_character(string, '');




When we free the string, we don't need a loop. Instead, we have a single free(). It's a good idea to reset the size and capacity so that the string object is consistent - it can be used again and/or freed again without harm:



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;



This is an important concept in object-oriented programming - objects have invariants that they guarantee are true at the start and end of every (public) operation. In this case, the invariants are that




  • data points to valid storage of at least capacity if capacity > 0 and is a null pointer otherwise.


  • size is not greater than capacity.


We can improve the printing so that it outputs any embedded NUL characters, just like C++ strings do:



void String_print(struct String* string)

if (fwrite(string->data, 1, string->size, stdout) != string->size)
exit(1); /* TODO: improve error reporting */




Note that this will now print the trailing NUL we added. We no longer need that to mark the end of string, so we can remove that line.




Modified code



#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

struct String
char* data;
size_t size;
size_t capacity;
;

void String_init(struct String* string)

string->data = NULL;
string->size = string->capacity = 0;


void String_allocate_space(struct String* string)

size_t new_capacity = string->capacity ? 2 * string->capacity : 1;
char *tmp = realloc(string->data, new_capacity);
if (!tmp)
/* error handling - c is still valid */
exit(1); /* TODO: improve error reporting */

string->data = tmp;
string->capacity = new_capacity;


void add_character(struct String* string, char ch)

if (string->size == string->capacity) // if current letter exceeds capacity
String_allocate_space(string);

string->data[string->size++] = ch; // append it


void String_read(struct String* string)

string->size = 0;
int ch;
while ((ch = getc(stdin)) != EOF && !isspace(ch))
add_character(string, (char)ch);



void String_print(struct String *restrict string, FILE *restrict stream)

if (fwrite(string->data, 1, string->size, stream) != string->size)
exit(1); /* TODO: improve error reporting */



void String_free(struct String* string)

free(string->data);
string->data = NULL;
string->size = string->capacity = 0;


int main()

struct String string;

String_init(&string);
String_read(&string);
String_print(&string, stdout);
String_free(&string);







share|improve this answer















share|improve this answer



share|improve this answer








edited Jun 25 at 17:46


























answered Jun 25 at 16:25









Toby Speight

17.2k13487




17.2k13487











  • thanks for the in deep review. i have to admit i didn't even expected to read much comments to this code because i considered the code to easy and short :-) I was quite wrong... One question i have hwat would be an better error handling? and why exit(1) not exit(-1)
    – Sandro4912
    Jun 25 at 18:34










  • One nit to pick. I would change "if (!tmp)" to "if (tmp == NULL)", because tmp is not a logical (true/false) condition, it's a comparison to a special value, the NULL pointer. FALSE and NULL just happen to be implemented as 0 in C, but that's not necessarily universally true.
    – jamesqf
    Jun 25 at 20:33






  • 3




    @james/hoffmale: It's a standard C (and C++) idiom to test the validity of a pointer by leaving the comparison to 0 implicit. Remember that a literal 0 always means a null pointer (whether or not the machine uses all-bits-zero for its representation). NULL doesn't "just happen" to be implemented as 0 - the Standard insists that it must be 0, even on hardware where the zero address is valid. In those cases, the implementation is required to evaluate a pointer holding the zero address as a "true" value.
    – Toby Speight
    Jun 26 at 7:35







  • 1




    Further reading: see the answers to Does Standard define null pointer constant to have all bits set to zero?, Why is address zero used for the null pointer? and Can I use if (pointer) instead of if (pointer != NULL)?.
    – Toby Speight
    Jun 26 at 7:42






  • 1




    Detail: "0 always means a null pointer" is amiss. 0 is always a constant int with value 0. Comparing that int to a pointer p, converts the int 0 to a null pointer. IAC, if (!tmp) is OK, yet as a minor point, I prefer if (tmp == NULL) as negations tend to be less clear than positive assertions - don't you not think is won't be otherwise ;-)? (Good review)
    – chux
    Jun 26 at 11:41
















  • thanks for the in deep review. i have to admit i didn't even expected to read much comments to this code because i considered the code to easy and short :-) I was quite wrong... One question i have hwat would be an better error handling? and why exit(1) not exit(-1)
    – Sandro4912
    Jun 25 at 18:34










  • One nit to pick. I would change "if (!tmp)" to "if (tmp == NULL)", because tmp is not a logical (true/false) condition, it's a comparison to a special value, the NULL pointer. FALSE and NULL just happen to be implemented as 0 in C, but that's not necessarily universally true.
    – jamesqf
    Jun 25 at 20:33






  • 3




    @james/hoffmale: It's a standard C (and C++) idiom to test the validity of a pointer by leaving the comparison to 0 implicit. Remember that a literal 0 always means a null pointer (whether or not the machine uses all-bits-zero for its representation). NULL doesn't "just happen" to be implemented as 0 - the Standard insists that it must be 0, even on hardware where the zero address is valid. In those cases, the implementation is required to evaluate a pointer holding the zero address as a "true" value.
    – Toby Speight
    Jun 26 at 7:35







  • 1




    Further reading: see the answers to Does Standard define null pointer constant to have all bits set to zero?, Why is address zero used for the null pointer? and Can I use if (pointer) instead of if (pointer != NULL)?.
    – Toby Speight
    Jun 26 at 7:42






  • 1




    Detail: "0 always means a null pointer" is amiss. 0 is always a constant int with value 0. Comparing that int to a pointer p, converts the int 0 to a null pointer. IAC, if (!tmp) is OK, yet as a minor point, I prefer if (tmp == NULL) as negations tend to be less clear than positive assertions - don't you not think is won't be otherwise ;-)? (Good review)
    – chux
    Jun 26 at 11:41















thanks for the in deep review. i have to admit i didn't even expected to read much comments to this code because i considered the code to easy and short :-) I was quite wrong... One question i have hwat would be an better error handling? and why exit(1) not exit(-1)
– Sandro4912
Jun 25 at 18:34




thanks for the in deep review. i have to admit i didn't even expected to read much comments to this code because i considered the code to easy and short :-) I was quite wrong... One question i have hwat would be an better error handling? and why exit(1) not exit(-1)
– Sandro4912
Jun 25 at 18:34












One nit to pick. I would change "if (!tmp)" to "if (tmp == NULL)", because tmp is not a logical (true/false) condition, it's a comparison to a special value, the NULL pointer. FALSE and NULL just happen to be implemented as 0 in C, but that's not necessarily universally true.
– jamesqf
Jun 25 at 20:33




One nit to pick. I would change "if (!tmp)" to "if (tmp == NULL)", because tmp is not a logical (true/false) condition, it's a comparison to a special value, the NULL pointer. FALSE and NULL just happen to be implemented as 0 in C, but that's not necessarily universally true.
– jamesqf
Jun 25 at 20:33




3




3




@james/hoffmale: It's a standard C (and C++) idiom to test the validity of a pointer by leaving the comparison to 0 implicit. Remember that a literal 0 always means a null pointer (whether or not the machine uses all-bits-zero for its representation). NULL doesn't "just happen" to be implemented as 0 - the Standard insists that it must be 0, even on hardware where the zero address is valid. In those cases, the implementation is required to evaluate a pointer holding the zero address as a "true" value.
– Toby Speight
Jun 26 at 7:35





@james/hoffmale: It's a standard C (and C++) idiom to test the validity of a pointer by leaving the comparison to 0 implicit. Remember that a literal 0 always means a null pointer (whether or not the machine uses all-bits-zero for its representation). NULL doesn't "just happen" to be implemented as 0 - the Standard insists that it must be 0, even on hardware where the zero address is valid. In those cases, the implementation is required to evaluate a pointer holding the zero address as a "true" value.
– Toby Speight
Jun 26 at 7:35





1




1




Further reading: see the answers to Does Standard define null pointer constant to have all bits set to zero?, Why is address zero used for the null pointer? and Can I use if (pointer) instead of if (pointer != NULL)?.
– Toby Speight
Jun 26 at 7:42




Further reading: see the answers to Does Standard define null pointer constant to have all bits set to zero?, Why is address zero used for the null pointer? and Can I use if (pointer) instead of if (pointer != NULL)?.
– Toby Speight
Jun 26 at 7:42




1




1




Detail: "0 always means a null pointer" is amiss. 0 is always a constant int with value 0. Comparing that int to a pointer p, converts the int 0 to a null pointer. IAC, if (!tmp) is OK, yet as a minor point, I prefer if (tmp == NULL) as negations tend to be less clear than positive assertions - don't you not think is won't be otherwise ;-)? (Good review)
– chux
Jun 26 at 11:41




Detail: "0 always means a null pointer" is amiss. 0 is always a constant int with value 0. Comparing that int to a pointer p, converts the int 0 to a null pointer. IAC, if (!tmp) is OK, yet as a minor point, I prefer if (tmp == NULL) as negations tend to be less clear than positive assertions - don't you not think is won't be otherwise ;-)? (Good review)
– chux
Jun 26 at 11:41












up vote
8
down vote













Since @TobySpeight has already posted a wonderful answer, I'm not going to repeat what he has already posted.



Just some small additional notes:



  • String_allocate_space allocates 1 chars worth of memory if the capacity is 0. This is barely enough to hold the terminating '' character, but nothing more. Maybe increase the default minimal allocation size a bit to be meaningful?


  • Also, regarding naming: If I see a function called String_free, I'd expect a function String_alloc that allocates and creates the String object in a well-defined state. (And consequently, String_free should then deallocate that String correctly.)


Also, this might just me, but from the task description I'd expect the solution to be a char *read_input(void) function. While the String "class" is nice, it seems like a bit of over-engineering for the task at hand.



For comparison, look at this solution:



#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#define INITIAL_INPUT_CAPACITY 4

char *read_input(void)
size_t capacity = INITIAL_INPUT_CAPACITY;
size_t size = 0;
char *str = malloc(capacity * sizeof(char));
int input;

while((input = getc(stdin)) != EOF)
if(isspace(input)) break;

str[size++] = (char)input;

if(size == capacity)
capacity *= 2;
char *temp = realloc(str, capacity * sizeof(char));

if(temp == NULL)
exit(-1);


str = temp;



str[size++] = '';

return str;


int main(void)
char *input = read_input();
puts(input);
free(input);

getchar();
return 0;






share|improve this answer























  • I was continuing my answer as you wrote this. I didn't think of the better initial capacity, though.
    – Toby Speight
    Jun 25 at 16:57










  • i think this code reflects more the ximplicity of c. since i do mostly c++ i think i tend to overengineer "easy" tasks
    – Sandro4912
    Jun 25 at 18:35










  • @Sandro4912: I don't think this is about "simplicity of C". C programs can very easily become very complex, even more complex than C++ with classes and templates. This is about using the right tool for the job, adhering to general language idioms and not doing more than what is needed.
    – hoffmale
    Jun 25 at 18:48














up vote
8
down vote













Since @TobySpeight has already posted a wonderful answer, I'm not going to repeat what he has already posted.



Just some small additional notes:



  • String_allocate_space allocates 1 chars worth of memory if the capacity is 0. This is barely enough to hold the terminating '' character, but nothing more. Maybe increase the default minimal allocation size a bit to be meaningful?


  • Also, regarding naming: If I see a function called String_free, I'd expect a function String_alloc that allocates and creates the String object in a well-defined state. (And consequently, String_free should then deallocate that String correctly.)


Also, this might just me, but from the task description I'd expect the solution to be a char *read_input(void) function. While the String "class" is nice, it seems like a bit of over-engineering for the task at hand.



For comparison, look at this solution:



#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#define INITIAL_INPUT_CAPACITY 4

char *read_input(void)
size_t capacity = INITIAL_INPUT_CAPACITY;
size_t size = 0;
char *str = malloc(capacity * sizeof(char));
int input;

while((input = getc(stdin)) != EOF)
if(isspace(input)) break;

str[size++] = (char)input;

if(size == capacity)
capacity *= 2;
char *temp = realloc(str, capacity * sizeof(char));

if(temp == NULL)
exit(-1);


str = temp;



str[size++] = '';

return str;


int main(void)
char *input = read_input();
puts(input);
free(input);

getchar();
return 0;






share|improve this answer























  • I was continuing my answer as you wrote this. I didn't think of the better initial capacity, though.
    – Toby Speight
    Jun 25 at 16:57










  • i think this code reflects more the ximplicity of c. since i do mostly c++ i think i tend to overengineer "easy" tasks
    – Sandro4912
    Jun 25 at 18:35










  • @Sandro4912: I don't think this is about "simplicity of C". C programs can very easily become very complex, even more complex than C++ with classes and templates. This is about using the right tool for the job, adhering to general language idioms and not doing more than what is needed.
    – hoffmale
    Jun 25 at 18:48












up vote
8
down vote










up vote
8
down vote









Since @TobySpeight has already posted a wonderful answer, I'm not going to repeat what he has already posted.



Just some small additional notes:



  • String_allocate_space allocates 1 chars worth of memory if the capacity is 0. This is barely enough to hold the terminating '' character, but nothing more. Maybe increase the default minimal allocation size a bit to be meaningful?


  • Also, regarding naming: If I see a function called String_free, I'd expect a function String_alloc that allocates and creates the String object in a well-defined state. (And consequently, String_free should then deallocate that String correctly.)


Also, this might just me, but from the task description I'd expect the solution to be a char *read_input(void) function. While the String "class" is nice, it seems like a bit of over-engineering for the task at hand.



For comparison, look at this solution:



#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#define INITIAL_INPUT_CAPACITY 4

char *read_input(void)
size_t capacity = INITIAL_INPUT_CAPACITY;
size_t size = 0;
char *str = malloc(capacity * sizeof(char));
int input;

while((input = getc(stdin)) != EOF)
if(isspace(input)) break;

str[size++] = (char)input;

if(size == capacity)
capacity *= 2;
char *temp = realloc(str, capacity * sizeof(char));

if(temp == NULL)
exit(-1);


str = temp;



str[size++] = '';

return str;


int main(void)
char *input = read_input();
puts(input);
free(input);

getchar();
return 0;






share|improve this answer















Since @TobySpeight has already posted a wonderful answer, I'm not going to repeat what he has already posted.



Just some small additional notes:



  • String_allocate_space allocates 1 chars worth of memory if the capacity is 0. This is barely enough to hold the terminating '' character, but nothing more. Maybe increase the default minimal allocation size a bit to be meaningful?


  • Also, regarding naming: If I see a function called String_free, I'd expect a function String_alloc that allocates and creates the String object in a well-defined state. (And consequently, String_free should then deallocate that String correctly.)


Also, this might just me, but from the task description I'd expect the solution to be a char *read_input(void) function. While the String "class" is nice, it seems like a bit of over-engineering for the task at hand.



For comparison, look at this solution:



#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>

#define INITIAL_INPUT_CAPACITY 4

char *read_input(void)
size_t capacity = INITIAL_INPUT_CAPACITY;
size_t size = 0;
char *str = malloc(capacity * sizeof(char));
int input;

while((input = getc(stdin)) != EOF)
if(isspace(input)) break;

str[size++] = (char)input;

if(size == capacity)
capacity *= 2;
char *temp = realloc(str, capacity * sizeof(char));

if(temp == NULL)
exit(-1);


str = temp;



str[size++] = '';

return str;


int main(void)
char *input = read_input();
puts(input);
free(input);

getchar();
return 0;







share|improve this answer















share|improve this answer



share|improve this answer








edited Jun 25 at 17:58


























answered Jun 25 at 16:48









hoffmale

4,225630




4,225630











  • I was continuing my answer as you wrote this. I didn't think of the better initial capacity, though.
    – Toby Speight
    Jun 25 at 16:57










  • i think this code reflects more the ximplicity of c. since i do mostly c++ i think i tend to overengineer "easy" tasks
    – Sandro4912
    Jun 25 at 18:35










  • @Sandro4912: I don't think this is about "simplicity of C". C programs can very easily become very complex, even more complex than C++ with classes and templates. This is about using the right tool for the job, adhering to general language idioms and not doing more than what is needed.
    – hoffmale
    Jun 25 at 18:48
















  • I was continuing my answer as you wrote this. I didn't think of the better initial capacity, though.
    – Toby Speight
    Jun 25 at 16:57










  • i think this code reflects more the ximplicity of c. since i do mostly c++ i think i tend to overengineer "easy" tasks
    – Sandro4912
    Jun 25 at 18:35










  • @Sandro4912: I don't think this is about "simplicity of C". C programs can very easily become very complex, even more complex than C++ with classes and templates. This is about using the right tool for the job, adhering to general language idioms and not doing more than what is needed.
    – hoffmale
    Jun 25 at 18:48















I was continuing my answer as you wrote this. I didn't think of the better initial capacity, though.
– Toby Speight
Jun 25 at 16:57




I was continuing my answer as you wrote this. I didn't think of the better initial capacity, though.
– Toby Speight
Jun 25 at 16:57












i think this code reflects more the ximplicity of c. since i do mostly c++ i think i tend to overengineer "easy" tasks
– Sandro4912
Jun 25 at 18:35




i think this code reflects more the ximplicity of c. since i do mostly c++ i think i tend to overengineer "easy" tasks
– Sandro4912
Jun 25 at 18:35












@Sandro4912: I don't think this is about "simplicity of C". C programs can very easily become very complex, even more complex than C++ with classes and templates. This is about using the right tool for the job, adhering to general language idioms and not doing more than what is needed.
– hoffmale
Jun 25 at 18:48




@Sandro4912: I don't think this is about "simplicity of C". C programs can very easily become very complex, even more complex than C++ with classes and templates. This is about using the right tool for the job, adhering to general language idioms and not doing more than what is needed.
– hoffmale
Jun 25 at 18:48












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f197218%2femulating-c-string-input-in-c%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation