Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 198 additions & 20 deletions c/snippets/fizzbuzz.c
Original file line number Diff line number Diff line change
@@ -1,28 +1,206 @@
#include <stdio.h>
#include <stdlib.h>

char* fizzbuzz (int n) {
if (n % 3 == 0 && n % 5 == 0) {
return "fizzbuzz";
} else if (n % 3 == 0) {
return "fizz";
} else if (n % 5 == 0) {
return "buzz";
} else {
char str[20];
sprintf(str, "%d", n);
// this is bad, returning from stack, tsk tsk
return str;
}
const int program_inputs[] = {
1, 3, 9, 5, 10, 1000, 15
};

/// =================== Approach 1: Global Buffer =======================
/**
* This approach uses a global buffer of chars to store the result of fizzbuzz.
* Sometimes when you implement arena allocator you reserve a large chunk of
* memory beforehand and then use it to allocate smaller chunks. However, this
* a very simple example so we'll just reuse the same buffer over and over.
*/

/**
* @brief declare a global buffer and zero initialize it. This syntax is called
* designated initializer and it's a C99 feature. Whenever you need to zero out a
* struct or an array, you can use this syntax.
* @example struct foo f = {0};
* @example int arr[10] = {0};
*/
char buffer[256] = {0};

// char const* ensures that the string is not modified.
char const* fizzbuzz_v1(int n) {

// This is approach to get the size of an array is valid since the compiler
// can see the declaration of the array, and it knows the size of each element.
// But in most cases, you won't be able to do this. For example, if you pass
// an array to a function, the compiler won't know the size of the array due to array
// decay.
// Working:
// Step 1: int buffer_size = sizeof(buffer) / sizeof(buffer[0]);
// Step 2: int buffer_size = sizeof(char[256]) / sizeof(char);
// Step 3: int buffer_size = 256 * sizeof(char) / sizeof(char);
// Step 4: int buffer_size = 256 * 1 / 1;
// Step 5: int buffer_size = 256;
const int buffer_size = sizeof(buffer) / sizeof(buffer[0]);
int written_len = 0;

if (n % 3 == 0 && n % 5 == 0) {
// This function will ensure the buffer will not overflow.
// If the buffer is not large enough, it will truncate the string.
written_len = snprintf(buffer, buffer_size, "fizzbuzz");
} else if (n % 3 == 0) {
written_len = snprintf(buffer, buffer_size, "fizz");
} else if (n % 5 == 0) {
written_len = snprintf(buffer, buffer_size, "buzz");
} else {
written_len = snprintf(buffer, buffer_size, "%d", n);
}

// There might be cases where sprintf fails, so we need to check for.
// You can ignore it if you want, but it's a good practice to check for
// if (written_len < 0) {
// sprintf(stderr, "Error: sprintf failed\n");
// exit(1);
// }

// Here we are making sure the string is null terminated.
buffer[written_len] = '\0';
return buffer;
}

void fizzbuzz_v1_test() {
const int len = sizeof(program_inputs) / sizeof(program_inputs[0]);
for (int i = 0; i < len; i++) {
printf("%s\n", fizzbuzz_v1(program_inputs[i]));
}
}

// ========================================================================

// ========================== Approach 2: Heap ============================

// char* indicates that it's your responsibility to free the memory.
char* fizzbuzz_v2(int n) {
// This is a heap allocated buffer. It's a good practice to check for
// malloc failure.
// Since malloc returns a void pointer, we need to cast it to char*. You can
// ignore the warning if you want, but it's a good practice to cast it.
// But now, it's your responsibility to free the memory.
// Malloc allocates memory in bytes, so we need to multiply the number of
// elements by the size of each element.
char* buffer = (char*)malloc(256 * sizeof(char));

if (buffer == NULL) {
fprintf(stderr, "Error: malloc failed\n");
exit(1);
}

int written_len = 0;

if (n % 3 == 0 && n % 5 == 0) {
written_len = snprintf(buffer, 256, "fizzbuzz");
} else if (n % 3 == 0) {
written_len = snprintf(buffer, 256, "fizz");
} else if (n % 5 == 0) {
written_len = snprintf(buffer, 256, "buzz");
} else {
written_len = snprintf(buffer, 256, "%d", n);
}

buffer[written_len] = '\0';
return buffer;
}

void fizzbuzz_v2_test() {
const int len = sizeof(program_inputs) / sizeof(program_inputs[0]);
for (int i = 0; i < len; i++) {
char* result = fizzbuzz_v2(program_inputs[i]);
printf("%s\n", result);

// Don't forget to free the memory.
// We free the memory after we are done with it, using "free" function.
free(result);
}
}

// ========================================================================

// ========================== Approach 3: Stack ============================

void fizzbuzz_v3(int n, char* buffer, int buffer_size) {
int written_len = 0;

if (n % 3 == 0 && n % 5 == 0) {
written_len = snprintf(buffer, buffer_size, "fizzbuzz");
} else if (n % 3 == 0) {
written_len = snprintf(buffer, buffer_size, "fizz");
} else if (n % 5 == 0) {
written_len = snprintf(buffer, buffer_size, "buzz");
} else {
written_len = snprintf(buffer, buffer_size, "%d", n);
}

buffer[written_len] = '\0';
}

void fizzbuzz_v3_test() {
const int len = sizeof(program_inputs) / sizeof(program_inputs[0]);

for (int i = 0; i < len; i++) {
// You can either put this buffer outside the loop or inside the loop.
// I don't think it matters much in this case.
char buffer[256] = {0};
fizzbuzz_v3(program_inputs[i], buffer, sizeof(buffer));
printf("%s\n", buffer);
}
}

// ========================================================================

// ========================== Approach 4: Static ============================
char const* fizzbuzz_v4(int n) {
// This is a static buffer, which means it's allocated in the data section
// of the program. It's not allocated on the stack or heap. It's unique and
// shared by all the instances of this function.
static char buffer[256] = {0};
const int buffer_size = sizeof(buffer) / sizeof(buffer[0]);

int written_len = 0;

if (n % 3 == 0 && n % 5 == 0) {
written_len = snprintf(buffer, buffer_size, "fizzbuzz");
} else if (n % 3 == 0) {
written_len = snprintf(buffer, buffer_size, "fizz");
} else if (n % 5 == 0) {
written_len = snprintf(buffer, buffer_size, "buzz");
} else {
written_len = snprintf(buffer, buffer_size, "%d", n);
}

buffer[written_len] = '\0';
return buffer;
}

void fizzbuzz_v4_test() {
const int len = sizeof(program_inputs) / sizeof(program_inputs[0]);
for (int i = 0; i < len; i++) {
printf("%s\n", fizzbuzz_v4(program_inputs[i]));
}
}

// ========================================================================

int main (void) {
puts(fizzbuzz(1));
puts(fizzbuzz(3));
puts(fizzbuzz(9));
puts(fizzbuzz(5));
puts(fizzbuzz(10));
puts(fizzbuzz(1000));
puts(fizzbuzz(15));
printf("======= fizzbuzz_v1: with global buffer =======\n");
fizzbuzz_v1_test();
puts(""); // Puts by default adds a newline character.

printf("======= fizzbuzz_v2: with heap =======\n");
fizzbuzz_v2_test();
puts(""); // Puts by default adds a newline character.

printf("======= fizzbuzz_v3: with stack =======\n");
fizzbuzz_v3_test();
puts(""); // Puts by default adds a newline character.

printf("======= fizzbuzz_v4: with static =======\n");
fizzbuzz_v4_test();
puts(""); // Puts by default adds a newline character.

return 0;
}
100 changes: 80 additions & 20 deletions cpp/snippets/fizzbuzz.cpp
Original file line number Diff line number Diff line change
@@ -1,28 +1,88 @@
#include <array>
#include <iostream>
#include <string>

std::string fizzbuzz(int x) {
if (x % 3 == 0 && x % 5 == 0) {
return "fizzbuzz";
} else if (x % 3 == 0) {
return "fizz";
} else if (x % 5 == 0) {
return "buzz";
} else {
std::string r = std::to_string(x);
return r;
}
// After C++17 or C++20, C++ added type deduction for class template argument.
// So, we can use std::array without specifying the type and size. Otherwise,
// we have to write like this:
// static constexpr std::array<int, 10> program_input = {...};
static constexpr std::array program_input = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
};

// ===================== Approach 1: Return the std::string (Original Code) =====================

// 1. "std::string" implements SOO (Small Object Optimization). SOO is a technique
// to avoid dynamic memory allocation by storing the data in the object itself using stack.
// The size of the object is the same as the size of the data it stores. However, if the
// size of the data exceeds the size of the object, the data is stored in the heap. In this
// case, the object stores a pointer to the data in the heap. Last time I checked, clang and gcc
// use 16 or 24 bytes for SOO. So, if the size of the data is 16 or 24 bytes or less, SOO is used. Otherwise,
// the data is stored in the heap.
// 2. C++ also performance RVO (Return Value Optimization) to avoid copying the return value. RVO is
// a technique to avoid copying the return value by constructing the return value in the caller's
// stack frame. In this case, the return value is constructed in the caller's stack frame, so the
// caller can use the return value without copying it.
// 3. Classes in C++ are copied using copy constructors. Copy constructors are called when the
// class is copied. If the class has a pointer, the pointer is copied. Therefore, the copy or move constructor will
// take care of returning string.
std::string fizzbuzz_v1(int x) {
if (x % 3 == 0 && x % 5 == 0) {
return "fizzbuzz";
} else if (x % 3 == 0) {
return "fizz";
} else if (x % 5 == 0) {
return "buzz";
} else {
return std::to_string(x);
}
}

void fizzbuzz_v1_test() {
for (auto x : program_input) {
std::cout << fizzbuzz_v1(x) << std::endl;
}
}

// ===============================================================================================

// ===================== Approach 2: Return the std::string by assigning to reference =====================

// 1. If we want to avoid copying the return value, we can use reference. In this case, we can use
// reference to std::string. However, we have to be careful about the lifetime of the return value.
// 2. Reference to std::string is a reference to a pointer. In c++'s world, reference is just a pointer but
// it's non-nullable and automatically dereferenced.
void fizzbuzz_v2(int n, std::string& res) {
if (n % 3 == 0 && n % 5 == 0) {
res = "fizzbuzz";
} else if (n % 3 == 0) {
res = "fizz";
} else if (n % 5 == 0) {
res = "buzz";
} else {
res = std::to_string(n);
}
}

void fizzbuzz_v2_test() {
std::string res;
for (auto x : program_input) {
fizzbuzz_v2(x, res);
std::cout << res << std::endl;
res.clear();
}
}

// ===============================================================================================


int main(void) {
std::cout << fizzbuzz(1) << std::endl;
std::cout << fizzbuzz(3) << std::endl;
std::cout << fizzbuzz(9) << std::endl;
std::cout << fizzbuzz(5) << std::endl;
std::cout << fizzbuzz(10) << std::endl;
std::cout << fizzbuzz(1000) << std::endl;
std::cout << fizzbuzz(15) << std::endl;

return 0;
std::cout<<"======= fizzbuzz_v1: Return the std::string ======="<<'\n';
fizzbuzz_v1_test();
std::cout << '\n';

std::cout<<"======= fizzbuzz_v2: Return the std::string by assigning to reference ======="<<'\n';
fizzbuzz_v2_test();
std::cout << '\n';
return 0;
}