5
\$\begingroup\$

I've been recently trying to learn C from the K&R Book The C Programming Language.

Currently I'm working on my skills on program control flow, which includes case statements.

To train my skills efficiently, I decided to go through the exercises in the book, and I've been learning fully offline and I have heard that many resources on C, alike the K&R Book, do not apprehend the new C99 conventions, that is why:

  1. I would like to know the best practices to implement in my programming style, in order to better enhance clarity.

  2. I am looking for tips on how to produce, find, visualize, and implement an algorithm for fixing a certain problem, especially in C.
    I've came from a Python environment, that is why in many cases I was liberate to use many syntactic sugars and declarative statements to fix my problems; unfortunately, this method of problem solving is not very adequate when it comes to C.

Write a program that read lines from the standard input, and replaces escape sequences such as \n or \t with their corresponding visualized character constant and outputs the modified message.

// main.c

/*
** Module for converting
** escape sequences to
** recognizable characters
** in a string.
*/


#include <stdio.h> // Input and Output functions.


/*
** Copy <to_escapify> to <result_storage>,
** while converting escape sequences to
** character constants:
** \n -> '\\' + 'n'
** \t -> '\\' + 't'
*/
void escapify(int to_escapify[], int result_storage[]);


void escapify(int to_escapify[], int result_storage[]) {
    /*
    ** initialize result_top_pointer with
    ** default value 0.
    **
    ** Sequence-loop through
    ** <to_escapify> elements
    ** using incrementing value
    ** <index> until index is
    ** equal to 100
    ** (assuming arrays are of lenght 100) then
    **
    **     if <to_escapify> <index> is is not a escape sequence then
    **         set <result_storage> <result_top_pointer>
    **         index to <to_escapify> <index>.
    **
    **         increment result_top_pointer by 1.
    **
    **     if <to_escapify> <index> is a escape sequence then
    **         set <result_storage> <result_top_pointer>
    **         to '\\' then
    **
    **         set <result_storage> <result_top_pointer + 1>
    **         to 'n' then
    **
    **         increment <result_top_pointer> by <2>.
    **
    */


    int result_top_pointer = 0;

    for (int index = 0; index <= 100; ++index) {
        switch (to_escapify[index]) {
            case '\t':
                result_storage[result_top_pointer] = '\\';
                result_storage[++result_top_pointer] = 't';

                ++result_top_pointer;
            case '\n':
                result_storage[result_top_pointer] = '\\';
                result_storage[++result_top_pointer] = 'n';

                ++result_top_pointer;
                break;
            default:
                result_storage[result_top_pointer++] = to_escapify[index];
        }
    }
}


int main() {
    int inputted_character;
    int to_escapify[100];
    int to_escapify_pointer = 0;

    int result_storage[100];


    for (int index = 0; index <= 100; ++index) {
        to_escapify[index] = 0;
    }


    while ((inputted_character = getchar()) != 'q') {
        to_escapify[to_escapify_pointer] = inputted_character;

        ++to_escapify_pointer;
    }


    escapify(to_escapify, result_storage);


    for (int index = 0; index <= 100; ++index) {
        putchar(result_storage[index]);
    }
    return 0;
}
\$\endgroup\$
5
  • 4
    \$\begingroup\$ "I've been recently trying to learn C from the K&R Book The C Programming Language." ==> People don't learn other languages from a 35 years old book, why C? Consider picking up Modern C (it covers C23), Seacord's Effective C, or something more recent than 1989 along with K&R. 2) C99 is not new, it is 25 years old, older than C# and Rust and others. \$\endgroup\$
    – Harith
    Commented May 13 at 17:34
  • 1
    \$\begingroup\$ "...do not apprehend the new C99 conventions" ==> I do not understand what you mean by this. Are you perchance worried that nobody supports C99 yet? \$\endgroup\$
    – Harith
    Commented May 13 at 17:40
  • \$\begingroup\$ @Harith likely "...do not comprehend the new C99 conventions" may make it clearer. \$\endgroup\$ Commented May 13 at 17:46
  • \$\begingroup\$ If it is as chux says, then even the Linux kernel has moved to GNU C11. :) \$\endgroup\$
    – Harith
    Commented May 13 at 19:28
  • 3
    \$\begingroup\$ Comments: comment the "why", not the "what". The comment inside your escapify adds nothing that isn't already there in the code, so it unneccesarily lengthens your program, and there's the risk you'll change your program later but forget to change the comment, making it wrong thus confusing the reader. A useful comment would have been int result_storage[200] // need twice the size because worst case each character inflates to two. Or if your algorithm isn't clear from the code, modified version of XXX, see <link/book>. But don't spam comments just for the sake of having many. \$\endgroup\$ Commented May 14 at 7:52

2 Answers 2

6
\$\begingroup\$

Buffer size

Older C code relied on hope, trust and had to deal with the high cost of code memory.

Do not code today without buffer limitations.

Instead pass in the buffer element count and re-write escapify() to never overrun buffers.

// escapify(to_escapify, result_storage);
escapify(to_escapify_pointer, to_escapify, 100, result_storage);

Do not read/write outside the buffer

Code reads one past the buffer size.

// for (int index = 0; index <= 100; ++index) {
for (int index = 0; index < 100; ++index) {
        putchar(result_storage[index]);
    }

and

    int result_storage[100];
    //for (int index = 0; index <= 100; ++index) {
    for (int index = 0; index < 100; ++index) {
        to_escapify[index] = 0;
    }

or simply

int result_storage[100] = { 0 };

Cope with buffer size and EOF

Current code is an infinite loop on end-of-file and over fills the buffer. getchar() typically returns 257 different values.

// while ((inputted_character = getchar()) != 'q') {
 while (to_escapify_pointer < 100 && (inputted_character = getchar()) != 'q' && 
            inputted_character != EOF) {
        to_escapify[to_escapify_pointer] = inputted_character;
        ++to_escapify_pointer;
    }

Use ample result buffer size

I'd expect result_storage[100]; to be 2x the size of to_escapify[100];

Characters size

Consider char or unsigned char instead of int for a character buffer element type.

Use const for buffers that do not change.

// void escapify(int to_escapify[], int result_storage[]);
void escapify(const int to_escapify[], int result_storage[]);

Avoid naked magic numbers

Rather than 100, here and there, use a macro

#define BSIZE 100
int to_escapify[BSIZE];
int result_storage[BSIZE * 2];
...

Naming

Calling an int to_escapify_pointer misleads. Use a better type and name like size_t escapify_length.

Function

escapify() should return a length of the result buffer used, so the calling code only needs to print/use the amount assgined.

\

Likely should escape the \ character to, otherwise it will be hard to un-escape the result.

            case '\\':
                result_storage[result_top_pointer] = '\\';
                result_storage[++result_top_pointer] = '\\';

                ++result_top_pointer;
                break;

Advanced

Use restrict for buffers that do not overlap others in a function.

This informs the complier that the buffers do not overlap and tells the coder to not call the function with such buffers.

Together with size info

// void escapify(const int to_escapify[], int result_storage[]);
size_t escapify(size_t to_length, const unsigned char * restrict to_escapify,
    size_t result_size, unsigned char * restrict result_storage);

Slight more common to put destination first

size_t escapify(size_t result_size, unsigned char * restrict result_storage
    size_t to_length, const unsigned char * restrict to_escapify);

Also I favor buffer size before buffer pointer even though it is less common.
Code to your group's coding style guide.

\$\endgroup\$
0
5
\$\begingroup\$

Use the correct prototype for main():

int main()

Prior to C23/C24, empty argument-list specifies that the function takes an unspecified number of arguments, not none. Change it to:

int main(void)

because this program does not make use of command-line arguments.

Do not be afraid to use C99 and above:

Contrary to your claim, C99 is not new. It is 25 years old, older than C#, Swift, Rust, Zig, Go, Kotlin, R, Elixir, Julia et cetera.

Some good books I can suggest are:

  • Modern C (this is more advanced).
  • Effective C (this is more beginner-friendly).
  • 21st Century C (this covers more than just C, I found it fairly well-written).

C99 is well-supported. Even the Linux Kernel has moved to GNU C11.

Comments:

/*
** Module for converting
** escape sequences to
** recognizable characters
** in a string.
*/

Why do the comments expand to so many newlines? This is written better as:

/* Module for converting escape sequences to recognizable characters in a string. */

Though, "module" is Python's terminology, not C's.

#include <stdio.h> // Input and Output functions.

Yes, io in stdio.h stands for input output, you need not mention that in a comment.

The valid indexing range is from 0 to n-1:

for (int index = 0; index <= 100; ++index) {
    putchar(result_storage[index]);
}

This invokes undefined behavior, as you're writing to out-of-bounds memory. Change it to:

for (int index = 0; index < 100; ++index) {
    putchar(result_storage[index]);
}

This happens in escapify() as well.

I'd also replace the 100 calls putchar() with fwrite().

Prefer using standard exit codes:

stdlib.h defines EXIT_SUCCESS and EXIT_FAILURE. Those are more readable and understandable than seeing magic values like 0, 1, -1 et cetera.

There's no need to specify a function prototype separately right before defining a function:

void escapify(int to_escapify[], int result_storage[]);


void escapify(int to_escapify[], int result_storage[]) {
    /*

Function definitions are function prototypes. You can omit the first line here. From the ISO C Standard (WG14/N1256, 6.9.1 about function definitions, paragraph 7):

If the declarator includes a parameter type list, the list also specifies the types of all the parameters; such a declarator also serves as a function prototype for later calls to the same function in the same translation unit.

\$\endgroup\$
1
  • \$\begingroup\$ "no need to specify a function prototype separately right before defining a function" --> Some warnings complain otherwise. Its more of a coding style issue. Yet I prefer to not repeat, \$\endgroup\$ Commented May 16 at 17:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.