Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[c/en] Wrapping fixes and some copy edits/small additions #4704

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
174 changes: 105 additions & 69 deletions c.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ contributors:
- ["Joshua Li", "https://github.com/JoshuaRLi"]
- ["Dragos B. Chirila", "https://github.com/dchirila"]
- ["Heitor P. de Bittencourt", "https://github.com/heitorPB/"]
- ["Chris Harding", "https://github.com/sjrct"]
---

Ah, C. Still **the** language of modern high-performance computing.
Expand All @@ -27,7 +28,8 @@ memory management and C will take you as far as you need to go.
>
> `-Wall -Wextra -Werror -O2 -std=c99 -pedantic`
>
> For information on what these flags do as well as other flags, consult the man page for your C compiler (e.g. `man 1 gcc`) or just search online.
> For information on what these flags do as well as other flags, consult the man
> page for your C compiler (e.g. `man 1 gcc`) or just search online.

```c
// Single-line comments start with // - only available in C99 and later.
Expand Down Expand Up @@ -63,26 +65,28 @@ enum days {SUN = 1, MON, TUE, WED = 99, THU, FRI, SAT};
// libraries for the headers.
// For your own headers, use double quotes instead of angle brackets, and
// provide the path:
#include "my_header.h" // local file
#include "my_header.h" // local file
#include "../my_lib/my_lib_header.h" //relative path

// Declare function signatures in advance in a .h file, or at the top of
// your .c file.
void function_1();
int function_2(void);
void function_1(void);
int function_2(int a, float b);
Comment on lines +73 to +74
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine the way it is and is easier to understand when it's simpler like this. I understand () is a compiler warning but in practice it doesn't matter if you don't even know how to access the variable length arguments of functions in C. I think mentioning (void) at the end is good but keeping it simple at the beginning is better.

Suggested change
void function_1(void);
int function_2(int a, float b);
void function_1();
int function_2(void);

Copy link
Contributor Author

@sjrct sjrct May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even with -Wall -Wextra gcc (at least version 13.2.0 for me) compiles calling functions with () for the parameter list with some arbitrary parameters without any warning. clang does give me some warning. C23 did change this behavior though, making (void) and () equivalent parameter lists for function declarations, and I know gcc is on v14 now, so my OS's version is probably a bit out of date and this will not become as relevant for new C in not too long.

Having been burning by that unexpected behavior in the past, it was my intuition to include it when making these changes originally, but with C23, putting it off to the end makes sense, or even leaving it out. I'll make some changes shortly


// At a minimum, you must declare a 'function prototype' before its use in any function.
// Normally, prototypes are placed at the top of a file before any function definition.
// At a minimum, you must declare a 'function prototype' before its use in any
// function. Normally, prototypes are placed at the top of a file before any
// function definition.
int add_two_ints(int x1, int x2); // function prototype
// although `int add_two_ints(int, int);` is also valid (no need to name the args),
// it is recommended to name arguments in the prototype as well for easier inspection
// although `int add_two_ints(int, int);` is also valid (no need to name the
// args), it is recommended to name arguments in the prototype as well for
// easier inspection

// Function prototypes are not necessary if the function definition comes before
// any other function that calls that function. However, it's standard practice to
// always add the function prototype to a header file (*.h) and then #include that
// file at the top. This prevents any issues where a function might be called
// before the compiler knows of its existence, while also giving the developer a
// clean header file to share with the rest of the project.
// any other function that calls that function. However, it's standard practice
// to always add the function prototype to a header file (*.h) and then #include
// that file at the top. This prevents any issues where a function might be
// called before the compiler knows of its existence, while also giving the
// developer a clean header file to share with the rest of the project.

// Your program's entry point is a function called "main". The return type can
// be anything, however most operating systems expect a return type of `int` for
Expand Down Expand Up @@ -125,7 +129,8 @@ int main (int argc, char** argv)
short x_short = 0;

// chars are defined as the smallest addressable unit for a processor.
// This is usually 1 byte, but for some systems it can be more (ex. for TMS320 from TI it's 2 bytes).
// This is usually 1 byte, but for some systems it can be more (ex. for TMS320
// from TI it's 2 bytes).
sjrct marked this conversation as resolved.
Show resolved Hide resolved
char x_char = 0;
char y_char = 'y'; // Char literals are quoted with ''

Expand Down Expand Up @@ -153,8 +158,8 @@ int main (int argc, char** argv)
// sizeof(obj) yields the size of the expression (variable, literal, etc.).
printf("%zu\n", sizeof(int)); // => 4 (on most machines with 4-byte words)

// If the argument of the `sizeof` operator is an expression, then its argument
// is not evaluated (except VLAs (see below)).
// If the argument of the `sizeof` operator is an expression, then its
// argument is not evaluated (except VLAs (see below)).
// The value it yields in this case is a compile-time constant.
int a = 1;
// size_t is an unsigned integer type of at least 2 bytes used to represent
Expand All @@ -171,15 +176,15 @@ int main (int argc, char** argv)
// You can initialize an array of twenty ints that all equal 0 thusly:
int my_array[20] = {0};
// where the "{0}" part is called an "array initializer".
// All elements (if any) past the ones in the initializer are initialized to 0:
// Elements (if any) past the ones in the initializer are initialized to 0:
int my_array[5] = {1, 2};
// So my_array now has five elements, all but the first two of which are 0:
// [1, 2, 0, 0, 0]
// NOTE that you get away without explicitly declaring the size
// of the array IF you initialize the array on the same line:
// NOTE that you get away without explicitly declaring the size of the
// array IF you initialize the array on the same line:
int my_array[] = {0};
// NOTE that, when not declaring the size, the size of the array is the number
// of elements in the initializer. With "{0}", my_array is now of size one: [0]
// NOTE that, when not declaring the size, the array's size is the number of
// elements in the initializer. With "{0}", my_array is now of size one: [0]
// To evaluate the size of the array at run-time, divide its byte size by the
// byte size of its element type:
size_t my_array_size = sizeof(my_array) / sizeof(my_array[0]);
Expand Down Expand Up @@ -249,7 +254,7 @@ int main (int argc, char** argv)
i2 * i1; // => 2
i1 / i2; // => 0 (0.5, but truncated towards 0)

// You need to cast at least one integer to float to get a floating-point result
// You need to cast at least one int to float to get a floating-point result
(float)i1 / i2; // => 0.5f
i1 / (double)i2; // => 0.5 // Same with double
f1 / f2; // => 0.5, plus or minus epsilon
Expand Down Expand Up @@ -366,13 +371,17 @@ int main (int argc, char** argv)
printf("\n");

// *****NOTES*****:
// Loops and Functions MUST have a body. If no body is needed:
// If the body of control expression is only one statement than parenthesises
// are optional, but often recommended:
sjrct marked this conversation as resolved.
Show resolved Hide resolved
for (jj = 0; jj < 5; jj++)
printf("%d\n", jj);

// Loops and if statements MUST have a body. If no body is needed:
int i;
for (i = 0; i <= 5; i++) {
; // use semicolon to act as the body (null statement)
}
// Or
for (i = 0; i <= 5; i++);
for (i = 0; i <= 5; i++); // use semicolon to act as the body (null statement)
sjrct marked this conversation as resolved.
Show resolved Hide resolved

// branching with multiple choices: switch()
switch (a) {
Expand Down Expand Up @@ -441,8 +450,9 @@ int main (int argc, char** argv)
// without warning.
printf("%d\n", (unsigned char) 257); // => 1 (Max char = 255 if char is 8 bits long)

// For determining the max value of a `char`, a `signed char` and an `unsigned char`,
// respectively, use the CHAR_MAX, SCHAR_MAX and UCHAR_MAX macros from <limits.h>
// For determining the max value of a `char`, a `signed char` and an
// `unsigned char`, respectively, use the CHAR_MAX, SCHAR_MAX and UCHAR_MAX
// macros from <limits.h>

// Integral types can be cast to floating-point types, and vice-versa.
printf("%f\n", (double) 100); // %f always formats a double...
Expand All @@ -453,9 +463,9 @@ int main (int argc, char** argv)
// Pointers
///////////////////////////////////////

// A pointer is a variable declared to store a memory address. Its declaration will
// also tell you the type of data it points to. You can retrieve the memory address
// of your variables, then mess with them.
// A pointer is a variable declared to store a memory address. Its declaration
// will also tell you the type of data it points to. You can retrieve the
// memory address of your variables, then mess with them.

int x = 0;
printf("%p\n", (void *)&x); // Use & to retrieve the address of a variable
Expand Down Expand Up @@ -493,9 +503,9 @@ int main (int argc, char** argv)
int* x_ptr = x_array;
// x_ptr now points to the first element in the array (the integer 20).
// This works because arrays often decay into pointers to their first element.
// For example, when an array is passed to a function or is assigned to a pointer,
// it decays into (implicitly converted to) a pointer.
// Exceptions: when the array is the argument of the `&` (address-of) operator:
// For example, when an array is passed to a function or is assigned to a
// pointer, it decays into (implicitly converted to) a pointer.
// Exception: when the array is the argument of the `&` (address-of) operator:
int arr[10];
int (*ptr_to_arr)[10] = &arr; // &arr is NOT of type `int *`!
// It's of type "pointer to array" (of ten `int`s).
Expand All @@ -514,22 +524,23 @@ int main (int argc, char** argv)

// You can also dynamically allocate contiguous blocks of memory with the
// standard library function malloc, which takes one argument of type size_t
// representing the number of bytes to allocate (usually from the heap, although this
// may not be true on e.g. embedded systems - the C standard says nothing about it).
// representing the number of bytes to allocate (usually from the heap,
// although this may not be true on e.g. embedded systems - the C standard
// says nothing about it).
int *my_ptr = malloc(sizeof(*my_ptr) * 20);
for (xx = 0; xx < 20; xx++) {
*(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx
} // Initialize memory to 20, 19, 18, 17... 2, 1 (as ints)

// Be careful passing user-provided values to malloc! If you want
// to be safe, you can use calloc instead (which, unlike malloc, also zeros out the memory)
// Be careful passing user-provided values to malloc! If you want to be safe,
// you can use calloc instead (which, unlike malloc, also zeros the memory)
int* my_other_ptr = calloc(20, sizeof(int));

// Note that there is no standard way to get the length of a
// dynamically allocated array in C. Because of this, if your arrays are
// going to be passed around your program a lot, you need another variable
// to keep track of the number of elements (size) of an array. See the
// functions section for more info.
// Note that there is no standard way to get the length of a dynamically
// allocated array in C. Because of this, if your arrays are going to be
// passed around your program a lot, you need another variable to keep track
// of the number of elements (size) of an array. See the functions section
// for more info.
size_t size = 10;
int *my_arr = calloc(size, sizeof(int));
// Add an element to the array
Expand All @@ -541,19 +552,19 @@ int main (int argc, char** argv)
}
my_arr[10] = 5;

// Dereferencing memory that you haven't allocated gives
// "unpredictable results" - the program is said to invoke "undefined behavior"
printf("%d\n", *(my_ptr + 21)); // => Prints who-knows-what? It may even crash.
// Dereferencing memory that you haven't allocated gives "unpredictable
// results" - the program is said to invoke "undefined behavior"
printf("%d\n", *(my_ptr + 21)); // => Prints who-knows-what? It may even crash

// When you're done with a malloc'd block of memory, you need to free it,
// or else no one else can use it until your program terminates
// (this is called a "memory leak"):
// or else no one else can use it until your program terminates (this is
// called a "memory leak"):
sjrct marked this conversation as resolved.
Show resolved Hide resolved
free(my_ptr);

// Strings are arrays of char, but they are usually represented as a
// pointer-to-char (which is a pointer to the first element of the array).
// It's good practice to use `const char *' when referring to a string literal,
// since string literals shall not be modified (i.e. "foo"[0] = 'a' is ILLEGAL.)
// It's good practice to use `const char *' when referring to a string literal
// since string literals shall not be modified (i.e. foo[0] = 'a' is ILLEGAL.)
Copy link
Collaborator

@verhovsky verhovsky May 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this pseudo code is clearer

Suggested change
// since string literals shall not be modified (i.e. foo[0] = 'a' is ILLEGAL.)
// since string literals shall not be modified (i.e. "foo"[0] = 'a' is ILLEGAL.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this change makes sense but I also think that indexing a string literal will probably make readers take a double take, and I wonder if we could just drop the parenthetical entirely.

const char *my_str = "This is my very own string literal";
printf("%c\n", *my_str); // => 'T'

Expand Down Expand Up @@ -635,11 +646,11 @@ printf("first: %d\nsecond: %d\n", first, second);
// Return multiple values.
// C does not allow for returning multiple values with the return statement. If
// you would like to return multiple values, then the caller must pass in the
// variables where they would like the returned values to go. These variables must
// be passed in as pointers such that the function can modify them.
int return_multiple( int *array_of_3, int *ret1, int *ret2, int *ret3)
// variables where they would like the returned values to go. These variables
// must be passed in as pointers such that the function can modify them.
int return_multiple(int *array_of_3, int *ret1, int *ret2, int *ret3)
{
if(array_of_3 == NULL)
if (array_of_3 == NULL)
return 0; //return error code (false)

//de-reference the pointer so we modify its value
Expand Down Expand Up @@ -672,15 +683,15 @@ printIntArray(my_arr, size);
// will print "arr[0] is: 1" etc
*/

// if referring to external variables outside function, you should use the extern keyword.
// if referring to external variables outside function, use the extern keyword.
int i = 0;
void testFunc() {
void testFunc(void) {
extern int i; //i here is now using external variable i
Comment on lines +688 to 689
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void testFunc(void) {
extern int i; //i here is now using external variable i
void testFunc() {
extern int i; // i here is now using external variable i

}

// make external variables private to source file with static:
static int j = 0; //other files using testFunc2() cannot access variable j
void testFunc2() {
void testFunc2(void) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void testFunc2(void) {
void testFunc2() {

extern int j;
}
// The static keyword makes a variable inaccessible to code outside the
Expand All @@ -693,6 +704,18 @@ void testFunc2() {
// declared with some other starting value.
//**You may also declare functions as static to make them private**

// NOTE that before C23, and unlike C++, functions taking no arguments without
sjrct marked this conversation as resolved.
Show resolved Hide resolved
// an explicit `void` inside the parameter list will be treated as taking an
// unknown number of arguments rather than no arguments.
void testFunc3(void) {
// Functions can be prototyped inside other functions
void foobie();
void bletch(void);

foobie(1, 2, 3); // This will give a warning at most, not an error
bletch(1, 2, 3); // This will produce an error
}

///////////////////////////////////////
// User-defined types and structs
///////////////////////////////////////
Expand All @@ -713,10 +736,16 @@ struct rectangle {
// due to potential padding between the structure members (this is for alignment
// reasons). [1]

void function_1()
void function_1(void)
{
struct rectangle my_rec = { 1, 2 }; // Fields can be initialized immediately

// Fields can also be initialized in an arbitrary order with the field name
struct rectangle my_rec2 = {
.height = 2,
.width = 1
};

// Access struct members with .
my_rec.width = 10;
my_rec.height = 20;
Expand Down Expand Up @@ -760,10 +789,10 @@ int areaptr(const rect *r)
// Function pointers
///////////////////////////////////////
/*
At run time, functions are located at known memory addresses. Function pointers are
much like any other pointer (they just store a memory address), but can be used
to invoke functions directly, and to pass handlers (or callback functions) around.
However, definition syntax may be initially confusing.
At run time, functions are located at known memory addresses. Function pointers
are much like any other pointer (they just store a memory address), but can be
used to invoke functions directly, and to pass handlers (or callback functions)
around. However, definition syntax may be initially confusing.

Example: use str_reverse from a pointer
*/
Expand All @@ -773,12 +802,13 @@ void str_reverse_through_pointer(char *str_in) {
f = &str_reverse; // Assign the address for the actual function (determined at run time)
// f = str_reverse; would work as well - functions decay into pointers, similar to arrays
(*f)(str_in); // Just calling the function through the pointer
// f(str_in); // That's an alternative but equally valid syntax for calling it.
// f(str_in); // That's an alternative but equally valid syntax for calling it
}

/*
As long as function signatures match, you can assign any function to the same pointer.
Function pointers are usually typedef'd for simplicity and readability, as follows:
As long as function signatures match, you can assign any function to the same
pointer. Function pointers are usually typedef'd for simplicity and readability,
as follows:
*/

typedef void (*my_fnp_type)(char *);
Expand Down Expand Up @@ -864,11 +894,16 @@ as the C file.
*/

/* A safe guard to prevent the header from being defined too many times. This */
/* happens in the case of circle dependency, the contents of the header is */
/* already defined. */
/* happens in the case of circlular dependencies, or such as when a header is */
/* included alongside a header that includes the same header itself. */
#ifndef EXAMPLE_H /* if EXAMPLE_H is not yet defined. */
#define EXAMPLE_H /* Define the macro EXAMPLE_H. */

/* This is a less verbose non-standard alternative to the include guards just */
/* mentioned. While non-standard it avoids errors from the two header files */
/* accidentally defining the same macro. Place it at the top of the header. */
#pragma once

sjrct marked this conversation as resolved.
Show resolved Hide resolved
/* Other headers can be included in headers and therefore transitively */
/* included into files that include this header. */
#include <string.h>
Expand All @@ -895,9 +930,10 @@ typedef struct Node
/* So can enumerations. */
enum traffic_light_state {GREEN, YELLOW, RED};

/* Function prototypes can also be defined here for use in multiple files, */
/* but it is bad practice to define the function in the header. Definitions */
/* should instead be put in a C file. */
/* Function prototypes can also be defined here for use in multiple files, */
/* but it is bad practice to define the function in the header. Definitions */
/* should instead be put in a C file. An uncommon exception is when defining */
/* static inline functions. */
Node createLinkedList(int *vals, int len);

/* Beyond the above elements, other definitions should be left to a C source */
Expand Down