new operator. The way we do this in
C is different from C++, but the ideas are all the
same. So please review your IC210 online course notes, e.g.
MUST UPDATE!IC210 L22, IC210 L23 and IC210 L24.
To give the briefest of reviews:
new operator in C++
are different than stack-allocated and
statically-allocated objects: they are not variables,
meaning that they are not bound to a name, and they don't
die until explicitly destroyed with the
delete operator.
Objects created by new are called
heap-allocated.
When you need arrays whose
size isn't determined until run-time (recall the
distinctions between compil time, link-time and run-time),
or when you need nodes to build a linked list, you need
heap-allocated objects. Since heap-allocated objects
don't have names, you can only refer to these objects via
pointers.
When you use new in C++, what's really happening
is that you are requesting a chunk of memory, in an area
called "the heap", to store objects in --- objects that live
indefinitely, unlike local variables. So int *p = new int;
causes a four-byte chunk (assuming 32-bit ints)
of memory in the heap to be allocated to your program, and
sets the pointer variable p to point to that
chunk.
When you later call delete p;, it tells the
memory management system that it can recycle those
four-bytes ... which means your program better not try to
derefence p afterwards. The pictures we draw
to represent this kind of thing are very explicit about
showing the heap-allocated objects as living off in space,
away from the function-call records on the stack.
new operator and destroyed with the
delete operator. Instead there are function calls
that accomplish the same things. Functions
malloc and calloc allocate memory,
and free gives it back to the memory management
system. The primary difference is that malloc/calloc simply
return a pointer to a chunk of memory, i.e. a sequence of
consecutive bytes. The programmer must explicitly cast that
pointer to one of the proper type, e.g. int* or
char**. Part and parcel with this is that the
programmer must figure out how many bytes are required to
store the object(s) they plan to put in that chunk of memory.
Fortunately, there is an operator sizeof to help.
From the man page:
SYNOPSIS
#include <stdlib.h>
void *malloc(size_t size);
void *calloc(size_t nelem, size_t elsize);
void free(void *ptr);
|
struct Node
{
int data;
struct Node *link;
};
int main(int argc, char **argv)
{
/* Create array of 10 double's, uninitialized */
double *A = (double*)malloc(10*sizeof(double));
/* Create array of argc ints, initialized to zero */
int *B = (int*)calloc(argc,sizeof(int));
/* Create one node for a linked list, unitialized */
struct Node *L = (struct Node*)malloc(sizeof(struct Node));
/* Create a string that is a copy if argv[0] */
char *s = (char*)malloc((strlen(argv[0]) + 1)*sizeof(char));
strcpy(s,argv[0]);
/* NOTE:
0) It's not enough to say "char *s". That gives a pointer,
but it doesn't yet point to anything. We need to allocate
an actual array of char's to copy argv[0] into.
1) We need 1 + length-or-argv[0] characters so we can
store the terminating '\0'
2) The "sizeof(char)" is actually not needed, since char's
have size one.
*/
/* Free up all that memory! */
free(A);
free(B);
free(L);
free(s);
return 0;
}
|
NOTE: malloc and calloc return NULL if they are
unsuccessful. How might that happen? Well you might run
out of memory! Like this program, which keeps allocating
huge chunks of memory, but never freeing them.
| mem2.c | Sample run: |
/* This program is made to run out of memory! */
#include <stdlib.h>
#include <stdio.h>
int main()
{
int *p, i = 0;;
while(1)
{
++i;
p = malloc(10000000*sizeof(int));
if (p == NULL) /* I.e. malloc failed */
{
fprintf(stderr,"Failed on iteration %i\n",i);
return 0;
}
}
}
|
bear[1] [~/]> gcc -o mem2 mem2.c bear[2] [~/]> ./mem2 Failed on iteration 92 |
int *p;p = &x3 + *pL->data, where
L is a pointer to a node* has.
3*x + 1.* means "pointer"; as in
int *p, which declares variable p to be of
type pointer-to-an-int, or "int*".
* means
"dereference a pointer", i.e. to get the object the
pointer points to. So assuming that p pointed to an int
with value 5, *p + 1 would evaluate to 6,
while ++*p would evaluate to 6 and
change the value of the int p points to to 6.
data
field is an array of ints and a variable L that
is a pointer to a Node, you might have
wonderful expressions like this:
(*L).data = (int*)malloc(n*sizeof(int)); /* Wow this is ugly! */in which the first * dereferences L, i.e. gets the node object that L points to, the second * indicates declares a pointer data type, and the third * is good old multiplication! Of course the fourth and fith *'s serve yet another purpose by delimiting comments. By the way, recall that this could also be written as
L->data = (int*)malloc(n*sizeof(int)); /* A bit less ugly. */because L->data is just a shorthand for
(*L).data.
You always need to keep the context in mind to know what's what.
Pointers give you the ability to write different expressions that refer to the same object, which are called aliases.
int x = 5; int *p = &x; /* Now x and *p refer to the same object! */
int *A = (int*)malloc(10*sizeof(int)); /* Dynamic array */ /* now A is the same as &A[0] */ int B[10]; /* Static array */ /* now B is the same as &B[0] */However, C/C++ does distinguish between dynamic arrays and static arrays. Static arrays don't require calls to malloc, but their size is hardcoded in, i.e. determined at compile time, and even though the array gets treated like a pointer, it's a pointer you can never change. So with
int A[5];, which defines a static array of five
ints, you can modify the ints that A points to, but the
pointer A itself cannot change, i.e. A = NULL is
not allowed. In fact, in this example A has type int * const,
which makes sense reading right-to-left. It gives: "constant
pointer to ints". So the pointer is constant, the ints in the
chunk of memory it points to can be modified. There are lots
of subtlties, unfortunately, with how arrays and pointers and
their initilizations interact. I'll just give one example,
because it might very easily get you:
char *s = "the";
s[0] = 'T'; /* this crashes the program, because s points to a constant string "the"
char r[] = "the";
r[0] = 'T'; /* this is OK, because the constant string "the" is actually copied into
a new static array r, which you may then modify. */
&x) rather than x itself.
#include <stdio.h>
#include <stdlib.h>
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
int main()
{
int x, y;
scanf("%i %i",&x,&y);
if (x > y)
swap(&x,&y);
printf("|y - x| = %i\n",y-x);
}
To define a function "swap" that swaps two integers,
we need to pass pointers to the integers, not
the integers themselves, because we want to modify the
integers. That means the arguments are
int* rather than int in the
prototype, and when the function is called you pass
pointers to x and y (i.e. &x and
&y) rather than x and y. Notice that
in the function swap, we have to dereference
parameters a and b, i.e. *a and
*b to refer to the integer values.
FILE* --- note that the definition for type FILE
comes from stdio.h. These streams are part of
the C language, not the Unix operating system. However, one
thing Unix and C share is a vision of these streams as nothing more
than a sequence of bytes. If you want to, you can read and
write to and from streams in this simple stream-of-bytes way
with the functions fread and fwrite.
This may not be so attractive for text files, but for
reading/writing binary data it's the way to go. To read or
write a single char at a time, you can use fgetc (to read) and
fputc (to write).
Opening a file, whether for reading or writing, is done with
the function fopen.
SYNOPSIS
#include <stdio.h>
FILE *fopen(const char *filename, const char *mode);
The first argument is the a string (char *) giving the filename (possibly including the
path), and the second argument is a string (char *) giving the
mode which for us will simply be "r" for read or "w"
for write. P. 138 of APUE describes the several other modes
that are available. The return value of fopen is the FILE*
that references the newly opened stream. If the file cannot
be opened, NULL is returned instead.
Closing a file is done with fclose.
#include <stdio.h>
int main()
{
char fname[64] = { '\0' };
while(fscanf(stdin,"%s",fname) == 1 && strlen(fname) != 0)
{
FILE* fs = fopen(fname,"r");
if (fs == NULL)
fprintf(stderr,"Error: File \"%s\" could not be opened!\n",fname);
else
{
char c;
while((c = fgetc(fs)) != EOF)
fputc(c,stdout);
fclose(fs);
}
}
return 0;
}
|
This program reads filenames from stdin
and echoes the contents of those files to standard
out (can you tell me why this is fundamentally
different than cat?). If a file
cannot be opened, it prints an error message, but
continues. Notice that I'm using fgetc and fputc to
read and write a byte at a time. The FILE* fs could
just as well be used with fscanf, by the way.
|