Because you broke my believability, I now demand you to come up with a good example of the difference between declarations and definitions!
"Declarations and definitions" of what specifically?
The target here is to provide OP with an intuitive understanding of the difference between 'declaration' and 'definition'.
In my post, I provided the short snippet from the C11 standard that best describes my own intuitive approach.
I was hoping you could think of something both technically correct, and easily understood. (As you know, we tend to have a bit different approach to these things. The different viewpoints might yield different ideas for what is a suitable example here.)
Here's the Collatz conjecture example:
We want to experiment with the
Collatz conjecture, printing '^' for each up+down step pair (
x ← 3x+1 followed by
x ← x/2) and 'v' for each down step (
x ← x/2), plus the number of steps needed to reach 1.
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
// We declare the two functions so that in their definition, they can recursively call each other.
// It is common to include the types of parameters, but omit the parameter names.
static uintmax_t collatz_up(FILE *, uintmax_t);
static uintmax_t collatz_down(FILE *, uintmax_t);
// The actual function.
uintmax_t collatz(FILE *out, uintmax_t x)
{
uintmax_t n;
if (x < 2) {
// x == 0 is invalid, and x == 1 is zero-length sequence.
n = 0;
} else
if (x & 1) {
// x is odd, so we go up.
n = collatz_up(out, x);
} else {
// x is even, so we go down.
n = collatz_down(out, x);
}
if (out) {
fputc('\n', out);
}
return n;
}
static uintmax_t collatz_up(FILE *out, uintmax_t x)
{
// Since x is odd, (3*x) is odd, and (3*x+1) is even.
// Thus, each ^ step is always followed by a v step,
// and we actually have (3*x+1)/2 = x + (x+1)/2.
if (out) {
fputc('^', out);
}
uintmax_t new_x = x + (x + 1)/2;
if ((uintmax_t)((2*new_x - 1) / 3) != x) {
// Overflow occurred
if (out) {
fputc('!', out);
}
return 0;
}
if (new_x < 2) {
return 2;
} else
if (new_x & 1) {
return 2 + collatz_up(out, new_x);
} else {
return 2 + collatz_down(out, new_x);
}
}
static uintmax_t collatz_down(FILE *out, uintmax_t x)
{
if (out) {
fputc('v', out);
}
uintmax_t new_x = x / 2;
if (new_x < 2) {
return 1;
} else
if (new_x & 1) {
return 1 + collatz_up(out, new_x);
} else {
return 1 + collatz_down(out, new_x);
}
}
int main(int argc, char *argv[])
{
const char *arg0 = (argc > 0 && argv && argv[0] && argv[0][0]) ? argv[0] : "(this)";
if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", arg0);
fprintf(stderr, " %s N [ N ... ]\n", arg0);
fprintf(stderr, "\n");
fprintf(stderr, "This outputs the number of Collatz conjecture steps from N to 1\n");
fprintf(stderr, "to standard output, after printing a ^ for each up+down step pair\n");
fprintf(stderr, "and v for each down step, to standard error.\n");
fprintf(stderr, "\n");
return (argc == 2) ? EXIT_SUCCESS : EXIT_FAILURE;
}
for (int arg = 1; arg < argc; arg++) {
uintmax_t val, n;
char *end = argv[arg];
errno = 0;
val = strtoumax(end, &end, 0);
if (errno || end == argv[arg] || *end != '\0' || val < 1) {
fprintf(stderr, "%s: Invalid N.\n", argv[arg]);
return EXIT_FAILURE;
}
printf("%ju: ", val);
fflush(stdout);
n = collatz(stderr, val);
fflush(stderr);
printf("%ju steps\n", n);
fflush(stdout);
}
return EXIT_SUCCESS;
}
If we compile that into
./ex, using for example
gcc -Wall -O2 above.c -o ex, then running
./ex 1161 703outputs
1161: ^v^^^v^^^^^^v^^^^^^v^vvv^v^^v^^vvv^vv^vv^^v^v^vv^^^^^vvv^^v^^^v^^^^v^vv^^^v^^v^^^^^^vv^^^^vvv^v^v^vvv^vv^^^vvvv^vvv
181 steps
703: ^^^^^^v^v^^^^^^v^^v^v^^^vv^vvv^^^^v^^^^^v^^^v^^^vv^v^vv^^^v^^^^vvvv^^^^^vvv^v^vvvvv^vv^^v^^vvv^^vv^^v^vv^vvv
170 steps
Alternatively,
./ex 1161 703 >/dev/null shows only the standard output part:
1161: 181 steps 703: 170 stepsThis is not the most sensible way to implement such a Collatz conjecture exploration program, but it shows how
declaring things that are
defined later, works.
The declaration itself does not generate any code or reserve any memory, it only tells the compiler enough to know how to refer to/access/call that thing.
The definition is what generates code or reserves memory.