#include <stdio.h>

/****
 *
 * This program illustrates same basics of strings, in particular how a string
 * is stored as an array of char.
 *
 * A very important convention about string variables in C is that they are
 * NULL TERMINATED.  This means that the end of the string is marked by the
 * null character, which is denoted '\0'.
 *
 * Given the null termination convention, a character array of 10 chars can
 * only hold a string up to 9 useful characters long.  This is because the last
 * character of any string needs to be a null.
 *
 * Since strings are stored as arrays, they are subject to the same bounds
 * rules as any other arrays.  That is, you cannot store more chars in a string
 * than its array is declared to hold.  And in the case of strings, you can
 * only store ONE FEWER character than the array is declared to hold.  Again,
 * this is because the last char of a string must always be null.
 *
 */

/* Prototypes for functions used at the end of main. */
void compute_with_strings(char s1[], char s2[], char s3[]);
void clobber_strings(char s1[], char s2[], char s3[]);

int main() {

    /*
     * Variable char_10 is an array that can hold up to 10 characters.  Because
     * of the null-termination convention, char_10 can only hold a string value
     * of 9 useful characters, since the last character must be null.
     *
     * Note that variable char_10 is not initialized in its declaration.  This
     * means it holds garbage to start out.
     */
    char char_10[10];

    /*
     * Variable string_10 is also an array of of up to 9 useful characters.
     * This variable is initialized to the 5-character string value "abcde".
     */
     char string_10[10] = "abcde";

    /*
     * Variable string_10_array_init is exactly the same kind of variable as
     * char_10 and string_10.  The difference here is in the style of
     * initialization value, which is a character-by-character array literal.
     *
     * The important thing is that variables string_10 and string_10_array_init
     * are exactly the same size and are initialized to exactly the same value.
     */
    char string_10_array_init[10] = {'a', 'b', 'c', 'd', 'e', '\0'};

    /*
     * Variable string_10_auto_sized is also string variable of up to 9 useful
     * characters.  In its declaration, the size of the array is determined
     * automatically from the length of the initializing string.  
     *
     * Note that the initial value of string_10_auto_sized is longer than
     * string_10 or string_10_array_init.  However, all three of these
     * variables are arrays with 10 elements.
     */
    char string_10_auto_sized[] = "abcdefghi";

    /*
     * Let's print out the values of the three initialized string variables.
     * String values are printed out using the %s formatter in printf.  When
     * printing with %s, printf totally relies on the null termination
     * convention.  If a string value is not null-terminated, printf will do
     * unpredictable things.
     */
    printf("string_10 = %s\n", string_10);
    printf("string_10_array_init = %s\n", string_10_array_init);
    printf("string_10_auto_sized = %s\n", string_10_auto_sized);

    /*
     * Now let's do some computation with the string variables, and print them
     * again.  See the comment for the compute_with_strings function for what
     * the computation is.
     */
    compute_with_strings(string_10, string_10_array_init, string_10_auto_sized);
    printf("Value of string variables after computation: %s, %s, %s\n",
	string_10, string_10_array_init, string_10_auto_sized);

    /*
     * In the preceding printf statements, we left out the printing of the
     * uninitialized variable char_10, since it's value is unknown garbage.
     * Let's live dangerously and print char_10, to see what happens.
     */
    printf("Uninitialized value of char_10 = %s\n", char_10);

    /*
     * We'll finish by doing something purposely foolish -- writing well past
     * the bounds of the three string variables.  See the comment for the
     * clobber_string function for what's likely to happen.
     */
    clobber_strings(string_10, string_10_array_init, string_10_auto_sized);
    printf("Value of string variables after computation: %s, %s, %s\n",
	string_10, string_10_array_init, string_10_auto_sized);

    return 0;

}

/**
 * This function illustrates some would-be computation with three string
 * parameters.  It doesn't do anything particularly useful -- it just sets the
 * first character of each string to 'X'.  The important concept this function
 * illustrates is that string parameters behave in exactly the same way as any
 * other type of array parameter.  Namely, changes made to string parameters
 * are reflected in the arrays at the calling site.  This means that in the
 * above print after this function is called, the values of the three strings
 * have been changed with 'X' to be the first character.
 */
void compute_with_strings(char s1[], char s2[], char s3[]) {
    s1[0] = 'X';
    s2[0] = 'X';
    s3[0] = 'X';
}

/**
 * This function flagrantly violates the rule of not storing values past the
 * end of any array.  It puts 'X' characters in the first 100 elements of the
 * three string parameters.  That's enough overwriting to likely cause some
 * damage.  While the exact nature of the damage is not predictable, it's quite
 * likely to cause a fatal error when the program is run.
 */
void clobber_strings(char s1[], char s2[], char s3[]) {
    int i;
    for (i = 0; i < 100; i++) {
	s1[i] = 'X';
	s2[i] = 'X';
	s3[i] = 'X';
    }
}