I more or less have an idea, but I'm not sure if I've even got the right idea and I was hoping maybe I was just missing something obvious. Basically, I have and array of strings (C strings, so basically an array of pointers to character arrays) like so:
char **words;
Which I don't know how many words I'll have in the end. As I parse the string, I want to be able to resize the array, add a pointer to the word, and move on to the next word then repeat.
The only way I can think of is to maybe start with a reasonable number and realloc every time I hit the end of the array, but I'm not entirely sure that works. Like I want to be able to access words[0], words[1], etc. If I had char **words[10] and called
realloc(words, n+4) //assuming this is correct since pointers are 4 bytes
once I hit the end of the array, if I did words[11] = new word, is that even valid?
Keep track of your array size:
size_t arr_size = 10;
And give it an initial chunk of memory:
char **words = malloc( arr_size * sizeof(char*) );
Once you have filled all positions, you may want to double the array size:
size_t tailIdx = 0;
while( ... ) {
if( tailIdx >= arr_size ) {
char **newWords;
arr_size *= 2;
newWords = realloc(words, arr_size * sizeof(char*) );
if( newWords == NULL ) { some_error() };
words = newWords;
}
words[tailIdx++] = get_next_word();
}
...
free(words);
That approach is fine ,although you may want to do realloc(words, n * 2) instead. calling realloc and malloc is expensive so you want to have to reallocate as little as possible and this means you can go for longer without reallocating (and possibly copying data). This is how most buffers are implemented to amortize allocation and copy costs. So just double the size of your buffer every time you run out of space.
You are probably going to want to allocate multiple blocks of memory. One for words, which will contain the array of pointers. And then another block for each word, which will be pointed to by elements in the words array.
Adding elements then involves realloc()ing the words array and then allocating new memory blocks for each new word.
Be careful how you write your clean up code. You'll need to be sure to free up all those blocks.
Related
I've taking many attempts at solving this problem but failed every time.
I have an array
char *array[1024] = {};
Now I would like to add an item to the array and would also access the items by numbers
For example:
array[0] would be the first item
array[1] would be the second
array[2] would be the third item
But also I would like to know how many items are in the array so I could use something like
for(int i = 0; i <= totalitemsinarray; i++) {
print(array[i]);
}
You cannot change the size of an array in C. You can however allocate a sufficiently large array and then fill it up with entries. First, declare an array with a sufficient size, say, 1024.
char *array[1024];
Then declare a variable fill that counts the number of used slots in array. Initialize it to 0 as 0 slots are used in the beginning. Then, each time you insert an item, increment fill:
array[fill++] = ...;
...
array[fill++] = ...;
Make sure that you never attempt to insert more than 1024 items into the array, C doesn't check that for you.
For a more flexible approach, use malloc() to allocate memory for the array and then periodically enlarge it with realloc() when it's full. If you increase the array size in exponential steps (say, multiply with Φ = 0.5 + 0.5 √2 ≈ 1.61), this runs in O(1) amortised time per entry inserted.
There is no way to do what you're asking directly with C. One option could be if you knew that only certain values were valid. For example, you have an array of char *s so often people use NULL as a flag/invalid value. In that case you could initialize your array to have all NULLs and use that to know the size of the array:
char *array[1024];
memset(array, 0, sizeof(array));
/* .... */
for (int i = 0; i < sizeof(array)/sizeof(char*); i++) {
if (array[i]) {
printf("%s\n", array[i]);
}
}
char *array[1024] = {};
First, that is an array with 1024 char pointers/strings. Those elements can be 0s or plain garbage. If you don't plan to set them all you may want to nullify the array.
For the matter of storing the values and the count you might want to have a look at structs. For example:
typedef struct elem {
int count;
char *value;
} elem;
Then elem.count would be the number and elem.value would be the value accordingly.
And then initialize them in a for loop.
The only really valid way to approach this, is to dynamically grow the array. Allocate the array on the heap, and manage two counts: 1. the count of currently used elements, and 2. the count of elements for which you currently have memory allocated. Something like this:
//the setup
size_t arrayLength = 0, allocatedSize = 8;
int* array = malloc(sizeof(*array) * allocatedSize);
//grow the array -> first check that we have space to add an element
if(arrayLength == allocatedSize) {
array = realloc(array, allocatedSize *= 2);
assert(array);
}
assert(arrayLength < allocatedSize);
//grow the array -> add an element
array[arrayLength++] = ...;
You see, the realloc() call is not too much hassle, but it will protect you from bugs when the requirements change. My experience is that any fixed limit in the code, as insanely large as it may seem to be, will eventually be exceeded, and miserable failure will result. The only safeguard is to use as much memory as needed everywhere.
I'm currently implementing a very simple JSON parser in C and I would like to be able to use mutable strings (I can do it without mutable strings, however I would like to learn the best way of doing them anyway). My current method is as follows:
char * str = calloc(0, sizeof(char));
//The following is executed in a loop
int length = strlen(str);
str = realloc(str, sizeof(char) * (length + 2));
//I had to reallocate with +2 in order to ensure I still had a zero value at the end
str[length] = newChar;
str[length + 1] = 0;
I am comfortable with this approach, however it strikes me as a little inefficient given that I am always only appending one character each time (and for the sake of argument, I'm not doing any lookaheads to find the final length of my string). The alternative would be to use a linked list:
struct linked_string
{
char character;
struct linked_string * next;
}
Then, once I've finished processing I can find the length, allocate a char * of the appropriate length, and iterate through the linked list to create my string.
However, this approach seems memory inefficient, because I have to allocate memory for both each character and the pointer to the following character. Therefore my question is two-fold:
Is creating a linked list and then a C-string faster than reallocing the C-string each time?
If so, is the gained speed worth the greater memory overhead?
The standard way for dynamic arrays, regardless of whether you store chars or something else, is to double the capacity when you grow it. (Technically, any multiple works, but doubling is easy and strikes a good balance between speed and memory.) You should also ditch the 0 terminator (add one at the end if you need to return a 0 terminated string) and keep track of the allocated size (also known as capacity) and the number of characters actually stored. Otherwise, your loop has quadratic time complexity by virtue of using strlen repeatedly (Shlemiel the painter's algorithm).
With these changes, time complexity is linear (amortized constant time per append operation) and practical performance is quite good for a variety of ugly low-level reasons.
The theoretical downside is that you use up to twice as much memory as strictly necessary, but the linked list needs at least five times as much memory for the same amount of characters (with 64 bit pointers, padding and typical malloc overhead, more like 24 or 32 times). It's not usually a problem in practice.
No, linked lists are most certainly not "faster" (how and wherever you measure such a thing). This is a terrible overhead.
If you really find that your current approach is a bottleneck, you could always allocate or reallocate your strings in sizes of powers of 2. Then you would only have to do realloc when you cross such a boundary for the total size of the char array.
I would suggest that it would be reasonable to read the entire set of text into one memory allocation, then go through and NUL terminate each string. Then count the number of strings, and make a array of pointers to each of the strings. That way you have one memory allocation for the text area, and one for the array of pointers.
Most implementations of variable-length arrays/strings/whatever have a size and a capacity. The capacity is the allocated size, and the size is what's actually used.
struct mutable_string {
char* data;
size_t capacity;
};
Allocating a new string looks like this:
#define INITIAL_CAPACITY 10
mutable_string* mutable_string_create_empty() {
mutable_string* str = malloc(sizeof(mutable_string));
if (!str) return NULL;
str->data = calloc(INITIAL_CAPACITY, 1);
if (!str->data) { free(str); return NULL; }
str->capacity = INITIAL_CAPACITY;
return str;
}
Now, any time you need to add a character to the string, you'd do this:
int mutable_string_concat_char(mutable_string* str, char chr) {
size_t len = strlen(str->data);
if (len < str->capacity) {
str->data[len] = chr;
return 1; //Success
}
size_t new_capacity = str->capacity * 2;
char* new_data = realloc(str->data, new_capacity);
if (!new_data) return 0;
str->data = new_data;
str->data[len] = chr;
str->data[len + 1] = '\0';
str->capacity = new_capacity;
}
The linked list approach is worse because:
You still need to do an allocation call every time you add a character;
It consumes a LOT more memory. This approach consumes up to sizeof(size_t) + (string_length + 1) * 2. That approach consumes string_length * sizeof(linked_string).
Generally, linked lists are less cache-friendly than arrays.
I have an array, say, text, that contains strings read in by another function. The length of the strings is unknown and the amount of them is unknown as well. How should I try to allocate memory to an array of strings (and not to the strings themselves, which already exist as separate arrays)?
What I have set up right now seems to read the strings just fine, and seems to do the post-processing I want done correctly (I tried this with a static array). However, when I try to printf the elements of text, I get a segmentation fault. To be more precise, I get a segmentation fault when I try to print out specific elements of text, such as text[3] or text[5]. I assume this means that I'm allocating memory to text incorrectly and all the strings read are not saved to text correctly?
So far I've tried different approaches, such as allocating a set amount of some size_t=k , k*sizeof(char) at first, and then reallocating more memory (with realloc k*sizeof(char)) if cnt == (k-2), where cnt is the index of **text.
I tried to search for this, but the only similar problem I found was with a set amount of strings of unknown length.
I'd like to figure out as much as I can on my own, and didn't post the actual code because of that. However, if none of this makes any sense, I'll post it.
EDIT: Here's the code
int main(void){
char **text;
size_t k=100;
size_t cnt=1;
int ch;
size_t lng;
text=malloc(k*sizeof(char));
printf("Input:\n");
while(1) {
ch = getchar();
if (ch == EOF) {
text[cnt++]='\0';
break;
}
if (cnt == k - 2) {
k *= 2;
text = realloc(text, (k * sizeof(char))); /* I guess at least this is incorrect?*/
}
text[cnt]=readInput(ch); /* read(ch) just reads the line*/
lng=strlen(text[cnt]);
printf("%d,%d\n",lng,cnt);
cnt++;
}
text=realloc(text,cnt*sizeof(char));
print(text); /*prints all the lines*/
return 0;
}
The short answer is you can't directly allocate the memory unless you know how much to allocate.
However, there are various ways of determining how much you need to allocate.
There are two aspects to this. One is knowing how many strings you need to handle. There must be some defined way of knowing; either you're given a count, or there some specific pointer value (usually NULL) that tells you when you've reached the end.
To allocate the array of pointers to pointers, it is probably simplest to count the number of necessary pointers, and then allocate the space. Assuming a null terminated list:
size_t i;
for (i = 0; list[i] != NULL; i++)
;
char **space = malloc(i * sizeof(*space));
...error check allocation...
For each string, you can use strdup(); you assume that the strings are well-formed and hence null terminated. Or you can write your own analogue of strdup().
for (i = 0; list[i] != NULL; i++)
{
space[i] = strdup(list[i]);
...error check allocation...
}
An alternative approach scans the list of pointers once, but uses malloc() and realloc() multiple times. This is probably slower overall.
If you can't reliably tell when the list of strings ends or when the strings themselves end, you are hosed. Completely and utterly hosed.
C don't have strings. It just has pointers to (conventionally null-terminated) sequence of characters, and call them strings.
So just allocate first an array of pointers:
size_t nbelem= 10; /// number of elements
char **arr = calloc(nbelem, sizeof(char*));
You really want calloc because you really want that array to be cleared, so each pointer there is NULL. Of course, you test that calloc succeeded:
if (!arr) perror("calloc failed"), exit(EXIT_FAILURE);
At last, you fill some of the elements of the array:
arr[0] = "hello";
arr[1] = strdup("world");
(Don't forget to free the result of strdup and the result of calloc).
You could grow your array with realloc (but I don't advise doing that, because when realloc fails you could have lost your data). You could simply grow it by allocating a bigger copy, copy it inside, and redefine the pointer, e.g.
{ size_t newnbelem = 3*nbelem/2+10;
char**oldarr = arr;
char**newarr = calloc(newnbelem, sizeof(char*));
if (!newarr) perror("bigger calloc"), exit(EXIT_FAILURE);
memcpy (newarr, oldarr, sizeof(char*)*nbelem);
free (oldarr);
arr = newarr;
}
Don't forget to compile with gcc -Wall -g on Linux (improve your code till no warnings are given), and learn how to use the gdb debugger and the valgrind memory leak detector.
In c you can not allocate an array of string directly. You should stick with pointer to char array to use it as array of string. So use
char* strarr[length];
And to mentain the array of characters
You may take the approach somewhat like this:
Allocate a block of memory through a call to malloc()
Keep track of the size of input
When ever you need a increament in buffer size call realloc(ptr,size)
I apologize if this is a waste of time and/or not what should be on this site, but I'm kind of out of ideas... I'm still a novice at programming, can't get a hold of my teacher for guidance, so... TO THE INTERNET!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void months( FILE* monthfp, char** monthGroup );
int main (void){
FILE *monthfp; /*to be used for reading in months from months.txt*/
char** monthGroup;
int i;
if (( monthfp = fopen ( "months.txt", "r" )) == NULL ){
printf( "unable to open months.txt. \n" );
exit ( 1 );
}
months( monthfp, monthGroup );
/*test so far*/
for ( i = 0; i < 12; i++ ){
printf( "%s", monthGroup[i] );
}
fclose( monthfp );
}
void months ( FILE* monthfp, char** monthGroup ){
/*****************************************
name: months
input: input file, data array
returns: No return. Modifies array.
*/
char buffer[50];
int count = 0;
while ( fgets( buffer, sizeof(buffer), monthfp ) != NULL ){
count++;
monthGroup = malloc( count * sizeof ( char* ));
monthGroup[count] = malloc( sizeof( buffer ) * sizeof( char ));
strcpy(monthGroup[ count - 1 ], buffer );
}
}
I'm compiling in C89, everything seems to work, except for a segmentation fault. Any guidance would be very much appreciated.
edit
Thanks to everyone who took the time to provide a little bit of insight into something I've been having trouble wrapping my head around. I feel like a little kid in a village of elders in a foreign land. Much appreciation for the courtesy and guidance.
I'm afraid you don't realize how far you are from getting it right. Sit tight, this is going to be long. Welcome to C.
char** monthGroup
All this really means is "a pointer-to-pointer-to-char". However, C has many reasons why you would want to point to something. In your case, the "inner" pointing is so that you can actually point at a sequence of chars in memory (which you colloquially treat as a "string", which C properly does not have), and the "outer" pointing is so that you can point at a sequence of those char*s, and treat that sequence as an "array" (even though it isn't; you're going to dynamically allocate it).
Here's the problem: When you pass in this char** that came from main, it doesn't actually point at anything. "That's fine", you say; "the function is going to make it point at some memory that I'll allocate with malloc()".
Nope.
C passes everything by value. The char** that months receives is a copy of the char** in main's chunk of local variables. You overwrite the pointer (with the result of the malloc call), write some pointers into that pointed-at memory (more malloc results), copy some data into those chunks of pointed-at memory... and then, at the end of the function, the parameter monthGroup (which is a local variable in months) no longer exists, and you've lost all that data, and the variable monthGroup in main is still unchanged at pointing at nothing. When you try to use it as if it points at something, boom you're dead.
So how do we get around this? With another level of pointing, of course, C properly does not have "pass by reference", so we must fake it. We accept a char***, and pass it &monthGroup. This is still a copied value, but it points directly into the local variable storage for that invocation of main (on the stack). That lets us write a value that will be visible in main. We assign the first malloc result to *monthGroup, and write pointers into that storage (*monthGroup[count]), etc.
Except we don't really want to do that, because it's incredibly ugly and confusing and hard to get right. Let's instead do what should be an incredibly obvious thing that you're meant to do and that basic instruction doesn't emphasize nearly enough: use the return value of the function to return the result of the calculation - that's why it's called the return value.
That is, we set up a char** in months (not accepting any kind of parameter for it), return it, and use it to initialize the value in main.
Are we done? No.
You still have some logical errors:
You re-allocate the "outer" layer within your while-loop. That's clearly not what you want; you're allocating several "strings", but only one "array", so that allocation goes outside the loop. Otherwise, you throw away (without properly deallocating them!) the old arrays each time.
Actually, you do want to do something like this, but only because you don't know in advance how many elements you need. The problem is that the new allocation is just that - a new allocation - not containing the previously-set-up pointers.
Fortunately, C has a solution for this: realloc. This will allocate the new memory, copy the old contents across (the pointers to your allocated "strings"), and deallocate the old chunk. Hooray! Better yet, realloc will behave like malloc if we give it a NULL pointer for the "old memory". That lets us avoid special-casing our loop.
You're using the value count incorrectly. The first time through the loop, you'll increment count to 1, allocate some space for monthGroup[1] to point at, and then attempt to write into the space pointed at by monthGroup[0], which was never set up. You want to write into the same space for a "string" that you just allocated. (BTW, sizeof(char) is useless: it is always 1. Even if your system uses more than 8 bits to represent a char! The char is the fundamental unit of storage on your system.)
Except not, because there's a simpler way: use strdup to get a pointer to an allocated copy of your buffer.
char** months(FILE* monthfp) {
char buffer[50];
int count = 0;
char** monthGroup = NULL;
while (fgets(buffer, sizeof(buffer), monthfp) != NULL) {
// (re-)allocate the storage:
monthGroup = realloc(monthGroup, count * sizeof(char*));
// ask for a duplicate of the buffer contents, and put a pointer to the
// duplicate sequence into the last element of the storage:
monthGroup[count - 1] = strdup(buffer);
}
return monthGroup;
}
Adjusting main to match is left as a (hopefully trivial) exercise. Please also read the documentation for realloc and strdup.
Are we done? No.
You should still be checking for NULL returns from realloc and strdup (since they both attempt to allocate memory, and thus may fail in that way in C), and you still need code to free the allocated memory.
And, as others pointed out, you shouldn't be assuming there will be 12 months. If you could assume that, you wouldn't be dynamically allocating monthGroup in the first place; you'd just use an array. So you need to communicate the size of the result "array" somehow (adding an explicit NULL pointer to the end is one way; another is to do the horribly ugly thing, pass in a char***, and use the return value to count the size).
C has pass-by-value semantics for function calls. This is a fancy way of saying that
int main() {
int a = 5;
addOneTo(a);
printf("%d\n", a);
return 0;
}
will print 5 no matter what addOneTo() does to its parameter.
In your code, your months() function sets its local variable monthGroup to the value returned by the first malloc(), then throws away that value when it returns.
You have a few choices here on how to fix this problem. You could malloc into monthGroup outside the months() function then pass it in. You could return the monthGroup value. Or you could pass a pointer to monthGroup for pass-by-reference semantics (char***).
In any case, I would encourage you to learn how to use a debugger (e.g. gdb) so you can see why it segfaults next time!
Your problem lies in the months function, specifically your understanding of how memory works.
Looking at your code:
monthGroup = malloc( count * sizeof ( char* ));
This line allocates a chunk of memory which is equivalent to an array of char * of size count.
monthGroup[count] = malloc( sizeof( buffer ) * sizeof( char ));
Here, a buffer is allocated of size sizeof (buffer) (the sizeof (char) is unneccesary). This is one problem here: you are assigning it to monthGroup[count]. Arrays in C are zero-base, which means that the array:
int array [3];
has elements:
array [0], array [1] and array [2]
array [3] is outside the memory of the array. So monthGroup[count] is also outside the memory of the array. You want monthGroup[count-1] instead. This will write to the last element in the array.
The second problem is that every time you do the first allocation, you lose the previously allocated data (this is know as a memory leak) and the data it contained.
To fix this, there are two approaches.
When allocating the array, copy the contents of the old array to the new array:
oldarray = monthGroup;
monthGroup = malloc (count * sizeof (char *))
memcpy (monthGroup, oldarray, count-1 * sizeof (char *));
free (oldarray);
monthGroup [count-1] = ....
or use realloc.
Use a linked list. A lot more complex this one but has the advantage of not requiring the arrays to be copied every time a new item is read.
Also, the monthGroup parameter doesn't get passed back to the caller. Either change the function to:
char **months (FILE *fp)
or:
void months (FILE *fp, char ***ugly_pointer)
Finally, the caller currently assumes that there are 12 entries and attempts to print each one out. What happens if there are fewer than 12, or more than 12? One way to cope is to use a special pointer to terminate the monthsGroup array, a NULL would do nicely. Just allocate one extra element to the array and set the last one to NULL.
To me the most obvious of your problems is that you pass char** monthGroup as a parameter by value, then malloc it inside the function months, and afterwards try to use it in the caller function. However, since you passed it by value, you only stored the malloced address in a local copy of monthGroup, which does not change the value of the original variable in main.
As a quick fix, you need to pass a pointer to monthGroup, rather than (a copy of) its current value:
int main (void){
...
char** monthGroup;
...
months( monthfp, &monthGroup );
...
}
void months ( FILE* monthfp, char*** monthGroup ){
...
*monthGroup = malloc( count * sizeof ( char* ));
...
}
This is ugly (IMHO there should be no real reason to use char*** in real code) but at least a step in the right direction.
Then, as others rightly mentioned, you should also rethink your approach of reallocating monthGroup in a loop and forgetting about the previous allocations, leaving memory leaks and dangling pointers behind. What happens in the loop in your current code is
// read the first bunch of text from the file
count++;
// count is now 1
monthGroup = malloc( count * sizeof ( char* ));
// you allocated an array of size 1
monthGroup[count] = malloc( sizeof( buffer ) * sizeof( char ));
// you try to write to the element at index 1 - another segfault!
// should be monthGroup[count - 1] as below
strcpy(monthGroup[ count - 1 ], buffer );
Even with the fix suggested above, after 10 iterations, you are bound to have an array of 10 elements, the first 9 of which are dangling pointers and only the 10th pointing to a valid address.
The completed code would be this:
int main (void)
{
FILE *monthfp; /*to be used for reading in months from months.txt*/
char **monthGroup = NULL;
char **iter;
if ((monthfp = fopen("c:\\months.txt", "r")) == NULL){
printf("unable to open months.txt. \n");
exit(1);
}
months(monthfp, &monthGroup);
iter = monthGroup;
/* We know that the last element is NULL, and that element will stop the while */
while (*iter) {
printf("%s", *iter);
free(*iter);
iter++;
}
/* Remember that you were modifying iter, so you have to discard it */
free(monthGroup);
fclose(monthfp);
}
void months(FILE *monthfp, char ***monthGroup)
{
/*****************************************
name: months
input: input file, data array
returns: No return. Modifies array.
*/
char buffer[50];
int count = 0;
while (fgets(buffer, sizeof(buffer), monthfp) != NULL){
count++;
/* We realloc the buffer */
*monthGroup = (char**)realloc(*monthGroup, count * sizeof(char**));
/* Here I'm allocating an exact buffer by counting the length of the line using strlen */
(*monthGroup)[count - 1] = (char*)malloc((strlen(buffer) + 1) * sizeof( char ));
strcpy((*monthGroup)[count - 1], buffer);
}
/* We add a terminating NULL element here. Other possibility would be returning count. */
count++;
*monthGroup = (char**)realloc(*monthGroup, count * sizeof(char**));
(*monthGroup)[count - 1] = NULL;
}
As said by others a char*** is ugly.
The principal error that I see immediately, is that your allocation for monthGroup will never make it back into your main.
I know it could be done using malloc, but I do not know how to use it yet.
For example, I wanted the user to input several numbers using an infinite loop with a sentinel to put a stop into it (i.e. -1), but since I do not know yet how many he/she will input, I have to declare an array with no initial size, but I'm also aware that it won't work like this int arr[]; at compile time since it has to have a definite number of elements.
Declaring it with an exaggerated size like int arr[1000]; would work but it feels dumb (and waste memory since it would allocate that 1000 integer bytes into the memory) and I would like to know a more elegant way to do this.
This can be done by using a pointer, and allocating memory on the heap using malloc.
Note that there is no way to later ask how big that memory block is. You have to keep track of the array size yourself.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char** argv)
{
/* declare a pointer do an integer */
int *data;
/* we also have to keep track of how big our array is - I use 50 as an example*/
const int datacount = 50;
data = malloc(sizeof(int) * datacount); /* allocate memory for 50 int's */
if (!data) { /* If data == 0 after the call to malloc, allocation failed for some reason */
perror("Error allocating memory");
abort();
}
/* at this point, we know that data points to a valid block of memory.
Remember, however, that this memory is not initialized in any way -- it contains garbage.
Let's start by clearing it. */
memset(data, 0, sizeof(int)*datacount);
/* now our array contains all zeroes. */
data[0] = 1;
data[2] = 15;
data[49] = 66; /* the last element in our array, since we start counting from 0 */
/* Loop through the array, printing out the values (mostly zeroes, but even so) */
for(int i = 0; i < datacount; ++i) {
printf("Element %d: %d\n", i, data[i]);
}
}
That's it. What follows is a more involved explanation of why this works :)
I don't know how well you know C pointers, but array access in C (like array[2]) is actually a shorthand for accessing memory via a pointer. To access the memory pointed to by data, you write *data. This is known as dereferencing the pointer. Since data is of type int *, then *data is of type int. Now to an important piece of information: (data + 2) means "add the byte size of 2 ints to the adress pointed to by data".
An array in C is just a sequence of values in adjacent memory. array[1] is just next to array[0]. So when we allocate a big block of memory and want to use it as an array, we need an easy way of getting the direct adress to every element inside. Luckily, C lets us use the array notation on pointers as well. data[0] means the same thing as *(data+0), namely "access the memory pointed to by data". data[2] means *(data+2), and accesses the third int in the memory block.
The way it's often done is as follows:
allocate an array of some initial (fairly small) size;
read into this array, keeping track of how many elements you've read;
once the array is full, reallocate it, doubling the size and preserving (i.e. copying) the contents;
repeat until done.
I find that this pattern comes up pretty frequently.
What's interesting about this method is that it allows one to insert N elements into an empty array one-by-one in amortized O(N) time without knowing N in advance.
Modern C, aka C99, has variable length arrays, VLA. Unfortunately, not all compilers support this but if yours does this would be an alternative.
Try to implement dynamic data structure such as a linked list
Here's a sample program that reads stdin into a memory buffer that grows as needed. It's simple enough that it should give some insight in how you might handle this kind of thing. One thing that's would probably be done differently in a real program is how must the array grows in each allocation - I kept it small here to help keep things simpler if you wanted to step through in a debugger. A real program would probably use a much larger allocation increment (often, the allocation size is doubled, but if you're going to do that you should probably 'cap' the increment at some reasonable size - it might not make sense to double the allocation when you get into the hundreds of megabytes).
Also, I used indexed access to the buffer here as an example, but in a real program I probably wouldn't do that.
#include <stdlib.h>
#include <stdio.h>
void fatal_error(void);
int main( int argc, char** argv)
{
int buf_size = 0;
int buf_used = 0;
char* buf = NULL;
char* tmp = NULL;
char c;
int i = 0;
while ((c = getchar()) != EOF) {
if (buf_used == buf_size) {
//need more space in the array
buf_size += 20;
tmp = realloc(buf, buf_size); // get a new larger array
if (!tmp) fatal_error();
buf = tmp;
}
buf[buf_used] = c; // pointer can be indexed like an array
++buf_used;
}
puts("\n\n*** Dump of stdin ***\n");
for (i = 0; i < buf_used; ++i) {
putchar(buf[i]);
}
free(buf);
return 0;
}
void fatal_error(void)
{
fputs("fatal error - out of memory\n", stderr);
exit(1);
}
This example combined with examples in other answers should give you an idea of how this kind of thing is handled at a low level.
One way I can imagine is to use a linked list to implement such a scenario, if you need all the numbers entered before the user enters something which indicates the loop termination. (posting as the first option, because have never done this for user input, it just seemed to be interesting. Wasteful but artistic)
Another way is to do buffered input. Allocate a buffer, fill it, re-allocate, if the loop continues (not elegant, but the most rational for the given use-case).
I don't consider the described to be elegant though. Probably, I would change the use-case (the most rational).