Print a circle of a chosen radius. Use a define for the radius so that you can test other sizes. This is for 12, for example:
$ ./a.out
*
**** ****
** **
* *
* *
* *
* *
* *
* *
* *
* *
* *
* *
* *
** **
**** ****
*
Hint: an equation for a circle is x^2 + y^2 = MYRAD^2
, ie.
y = sqrt(MYRAD * MYRAD - x * x);
Obviously, there are more ways how to do it.
- The easiest way is to use a two-dimensional array and go through the
x
axis and fill the correspondingy
value before printing out the array line by line.- To print the array, instead of printing it a character by character,
we can also use array lines as strings if we terminate each line first
with a
\0
.
- To print the array, instead of printing it a character by character,
we can also use array lines as strings if we terminate each line first
with a
- You could print the output line by line, that is, figure out what to print on each line. You do not need a two-dimensional array then, and speaking of which, not even an array at all.
You can find the sqrt()
function declaration in <math.h>
. You can also use
memset()
from string.h
to set a piece of memory to a specific byte (=
character).
On Linux and possibly other systems, you will need to link with the math library
to get the sqrt()
function code, i.e:
$ gcc -lm warm-up.c
An extension: fill the circle up.
$ ./a.out
*
*********
*************
***************
*****************
*******************
*********************
*********************
***********************
***********************
***********************
***********************
*************************
***********************
***********************
***********************
***********************
*********************
*********************
*******************
*****************
***************
*************
*********
*
🔑 circle.c
A simplified solution:
Part of the standard since C90.
- The
fopen("path", "mode")
opens a file and returns a pointer to an opaqueFILE
type. That pointer serves as a handle.- Getting
NULL
means an error. - The
mode
argument controls the behavior: read (r
), write (w
), append (a
).- The
+
adds the other mode (write for read and vice versa, read for append). - Write mode
w
creates the file if it does not exist, and truncates it if it does exist.
- The
- The
fclose
closes the handle - Important to avoid resource leak (fopen
can allocate both memory and file descriptor).
- Getting
FILE *fp;
/* Choose any other file you have on your system. */
if ((fp = fopen("/etc/passwd", "r")) == NULL)
err(1, "fopen");
/* process the file... */
if (fclose(fp) != 0)
err(1, "fclose");
- The
b
binary mode usually does not have any effect. - The
freopen
can be used to associate the standard streams (stderr
,stdin
, orstdout
) with a file- That means e.g. reading the standard input would automatically read from a specific file, if you wanted that.
-
printf(...)
is equivalent tofprintf(stdout, ...)
. However, you can print directly tostderr
withfprintf
.
fprintf(stderr, "Error happened: %s\n", "some error");
🔧 Write a code that opens the same file in an cycle (until fopen()
fails) without calling fclose()
on the handle. After how many iterations does
it fail on your system?
-
fprintf
works likeprintf
but prints to a specific stream -
fscanf
- Basically parses text input from a stream according to format string
- After the format string, all argument must be pointers.
-
fputs
/fgets
- send/read string to/from a stream
fputs("hello, world\n", stdout);
-
fputc
/fgetc
- send/readchar
to/from a stream
fputc('x', stderr);
-
fwrite
/fread
- for writing/reading binary data (such as structures or raw numeric types) -
feof
/ferror
- determine if the last I/O operation hit end of file or error.
The fread
() reads a selected number of items of a given size to memory. We
can use either an array or we can directly read to a variable through an
operator address-of. In our case, we will be reading a file byte by byte, so we
can give fread
() just an address of a character variable.
You can ignore the restrict
keyword, it is intended as a hit for a compiler
optimization, and also just accept now that we can assign any pointer to a void *
pointer.
size_t fread(void *restrict ptr, size_t size, size_t nmemb,
FILE *restrict stream);
Now we will read the file in chunks of one byte only. Therefore we can use an
address-of
operator on a char
object.
char c;
FILE *fp;
/* Choose any other file you have on your system. */
if ((fp = fopen("/etc/passwd", "r")) == NULL)
err(1, "fopen");
/*
* fread() returns a number of *items* read. In our case, it's the same as
* number of bytes as we read it one byte at a time.
*/
while (fread(&c, sizeof (c), 1, fp) == 1) {
putchar(c);
}
fclose(fp);
🔧 Note that you could read more characters at a time. However, keep in
mind the 2nd argument is the size of the element read, and the 3rd argument is
how many elements we read in one call. Remember that using an array name a
is
the same as &a[0]
. For example:
char a[16];
...
while ((n = fread(a, sizeof (a[0]), sizeof (a) / sizeof (a[0]), fp)) > 0) {
/* Process the bytes here. */
/* If we read less elements than requested, we hit end of file or error. */
if (n < (sizeof (a) / sizeof (a[0])))
break;
}
What could happen if reading bytes into character array and the 2nd and 3rd arguments are swapped ?
Check manual page for fread
() and ignore for now that the 1st argument is
of type void *
, we will get there later. As mentioned above, you can safely
put there an array or an address of a variable.
🔧 Check the man page for fwrite
() and modify the code so that what is
read from the file you write to some other file. The file should be created if
it does not exist. If it exists, its contents should be truncated. Do not
forget to open the output file for writing. All the details are in the man
page.
When reading/writing to a file using the above function, the current position changes accordingly. However, the position can be manipulated without performing any I/O.
-
fseek
moves the position.- The
whence
parameter has 3 possible values and makes theoffset
parameter relative to:-
SEEK_SET
- the beginning of the file -
SEEK_END
- the end of the file -
SEEK_CUR
- the current location of the cursor in the file
-
- The
-
ftell
returns the current position in the file.
🔧 By seeking, write code that creates a file that has every
4-th character a upper case letter of alphabet (A
to Z
) and then
prints the contents of the file byte by byte. Print the non-printable bytes
in hexadecimal (with 0x
prefix).
int *p;
sizeof (p)
is a size of a p
variable which holds an address, which
corresponds to the address size. In our case either 32 or 64 bits depending
on how your code was compiled.
#include <stdio.h>
int
main(void)
{
int *p;
printf("%zu\n", sizeof (p));
}
$ cc main.c
$ file a.out
a.out: Mach-O 64-bit executable x86_64
$ ./a.out
8
Here the sizeof (*p)
is equivalent to sizeof (int)
as that is what the type
of *p
is.
In C, there is a strong relationship between arrays and pointers. Most of the time, you can use array notation with pointers, and pointer notation with arrays, but not always.
By definition, the value of an array identifier or an expression of an array type is the address of the first array element (an element zero).
int *pa;
int a[4] = { 1, 2, 3, 4 };
pa = &a[0]; // means &(a[0]) as [] is of higher precedence than &
pa = a; // this gets the same result as above
p[0] = 3; // the array is now { 3, 2, 3, 4 }
*(a + 1) = 5; // the array is now { 3, 5, 3, 4 }
❗ As you can see, incrementing a pointer increments the value by the size of the object the pointer points to. That was already mentioned in the pointer intro. Another example:
int a[] = { 0, 1, 2 };
int *p = a;
printf("%d\n", *(p + 2)); // will print 2
❗ While you can work with arrays and pointers together, arrays are not pointers and pointers are not arrays. To work with a pointer, the generated code must first get the value of the pointer identifier, then work with it. With arrays, the generated code directly uses the memory address the array starts at. See below.
As just said, the generated code directly uses the memory address of an array. So, you cannot do the following:
int a[1];
int aa[1];
++a; // error
a = aa; // error
When assigning to an array element, the compiler generates code that assigns to a piece of memory representing the element using an offset from the beginning of the array.
int a[1];
a[0] = 15;
The generated assembler code (e.g. objdump -d
from LLVM) for the above assigns
15 directly to the piece of memory on the stack (started by the base pointer
rbp
) that represents the first array element. That also hints that one cannot
assign to an array identifier in C as it would need a special semantics that
would not fit the way C works with arrays.
100000fa6: c7 45 fc 0f 00 00 00 movl $15, -4(%rbp)
We already went through basics on strings and string literals.
Given that a string constant is internally used to initialize an array of
char
s, and an array name represents its first element memory address, the
value of a string is an address of its first character.
We already know that one can use an array notation with pointers. So, if you really wanted, you could do something like this:
printf("%c\n", "hello, world"[1]); // will print `e`
or even this:
printf("%c\n", 1["hello, world"]); // will also print `e`
Given that a string constant is a pointer to its first character, we can use it like this:
char *p = "hello";
printf("%c\n", p[1]); // will print 'e'
❗ Pointer initialized with a string literal may not
be changed in the same way as an array. The internal array created and
initialized from the string literal is read-only by the specification. Writing
to it is an undefined behavior. Writing to it with gcc
and clang
will crash
the program. However, for example, Oracle Developer
Studio
used to put such arrays into read-write memory by default in older versions.
So, working code compiled with an older version of that compiler and modifying
string literals would crash if compiled with those other two.
And as you can use a pointer notation with arrays (we already mentioned that the array name is a synonym for the address of its first element), you can do this:
char a[] = "hello, world";
printf("%c\n", *(a + 1)); // will print 'e'
Valid pointer operations are:
- Assignment of pointers of the same type.
- Adding and subtracting a pointer and an integer.
- Subtracting and comparing two pointers to members of the same array.
- Assigning and comparing a pointer to zero.
All other pointer arithmetic are invalid operations and may or may not trigger a warning.
More specifically, assigning an integer to a pointer is not a valid operation, usually you get a warning.
int
main(void)
{
int *p = 0x1234; // is not a valid behavior
}
$ cc int-to-ptr.c
int-to-ptr.c:4:7: warning: incompatible integer to pointer conversion initializing 'int *' with an
expression of type 'int' [-Wint-conversion]
int *p = 0x1234; // is not a valid behavior
^ ~~~~~~
1 warning generated.
❗ When an array name is passed to a function, what is actually passed is the address of the first element and the local variable representing the argument within the function is of a type of pointer to the array element. So, you can just declare the argument as an pointer to an element type and pass in an array name.
The reason for this is that as C can only pass arguments by value, copying array data to function arguments would be quite inefficient.
Even if you declare a function argument as an array, the argument is always treated as a pointer. The optional array size is accepted but ignored. Do not use it as it is only confusing. See for example 6.9.1 Function definitions, paragraph (10) in C99.
void
myfn(int a[])
{
/*
* 'a' here is a pointer to int, not an array of ints. So, you can
* assign to it, for example (which you cannot do with an array).
*/
a = NULL;
}
Some compilers even notice that the sizeof
operator is applied on an array
passed into a function and produce a warning about it (clang with
-Wsizeof-array-argument
).
$ clang -Wsizeof-array-argument array-as-argument.c
array-as-argument.c:15:25: warning: sizeof on array function parameter will
return size of 'char *' instead of 'char [20]' [-Wsizeof-array-argument]
printf("%zu\n", sizeof (a));
^
array-as-argument.c:12:11: note: declared here
myfn(char a[20])
^
1 warning generated.
However, it only applies to the first array dimension. That is, a multi-dimensional array is just a 1-dimensional array of elements that are of an array type. Note that we are getting ahead of ourselves a little bit here, more on that will be later, especially on reading more complex declarations.
$ cc src/passing-arrays.c
src/passing-arrays.c:14:14: warning: incompatible pointer types passing 'int (*)[3]' to parameter of
type 'int (*)[2]' [-Wincompatible-pointer-types]
myfn(p2); // will trigger a warning
^~
src/passing-arrays.c:2:10: note: passing argument to parameter 'a' here
myfn(int a[2][2])
^
1 warning generated.
In C, function arguments are always passed by value. To change a value of an argument, thus emulating a calling by reference, you need to pass pointers. That means, you pass a pointer by value so that you know what piece of memory to modify.
The following will not do what one might naively assume it would. What you actually swap are values on a stack or in registers while the original objects were left untouched.
int
swap(int a, int b)
{
int n = a;
a = b;
b = n;
}
🔧 modify the code above so that swap
() that actually swaps the values
of the two int
s. Hint: aside from the dererence operator *
, you will also
need the address-of operator &
- see
pointer basics
Note that home assignments are entirely voluntary but writing code is the only way to learn a programming language.
🔧 Get a file size using the standard IO API (that is, lseek(2)
is
prohibited even if you know it).
🔧 Create an array of int
values (of arbitrary positive length with
values ranging from INT_MAX
to 0
), write the array to a file, read the
values into another array and print them to the standard error output. Between
the writing and reading, the file handle has to remain open. Use the same file
handle for reading and writing. Use xxd(1)
(or od(1)
, hexdump(1)
) to
verify the content of the file. Thus it is handy to start with INT_MAX
and
e.g. divide by 2 for each successive value.
🔧 Use the file created by the previous program. Read the values from the end of the file to the beginning of the file one by one without knowing the file size and print the numbers to the standard error output.
🔧 create a text file where each line begins with integer followed by space and a string, e.g.:
42 towel
13 dwarfs and Snow White
Read the file using fscanf()
and print the values (i.e. integer and a string)
from each line to standard output.
Write a function that copies a string to a character array, including the terminating null character. It returns the number of characters copied.
Note that a string constant is read-only by definition, and writing to it is an undefined behavior. That is:
char *s = "hello"
// Do not do this. Different compilers may act differently.
s[0] = 'H';
To write the function, take :eyes: tasks/string-to-array-copy.c and implement it there. See the comment inside.
Verify the code prints exactly what it says in the comments. gcc -Wall -Wextra implement-function.c
must be clear of any warnings.