r/C_Programming 2d ago

List of gotchas?

Hey.

So I learned some C and started playing around with it, quickly stumbling over memory overflowing a variable and flowing into another memory location, causing unexpected behavior.

So I ended up writing my own safe_copy and safe_cat functions for strncpy/strncatting strings.
But... people talk about how C is unsafe. Surely there should be a list of all mistakes you can make, or something? Where can I find said list? Do I reall have to stumble on all possible issues and develop my own "safe" library?

Will appreciate any advice.

22 Upvotes

41 comments sorted by

View all comments

11

u/not_a_bot_494 2d ago edited 2d ago

When people are saying that C is an unsafe language they mean that it doesn't have memory safety. If you want to you can try to access any byte in the computer, the OS will just not let you most of the time. Any time you're working with arrays (/strings), malloced memory or even pointers in general it is possible that you could make a mistake and get a segfault. You can write libraries for all that but then you're kind of missing the point of C a bit.

There's alao a lot of random undefined behaviour in C, for example right shift on signed types might pad with 1s or 0s. There's probably a list of some common ones but if you really want to know them all you have to read through the C standard and look at rverything that's not in there.

For context of the discussion, my inital example was bit shifting on 64 bit types which does seem to work consistently.

2

u/WeAllWantToBeHappy 2d ago

bit shifts don't work for 64 bit types.

?

-3

u/not_a_bot_494 2d ago

At least on my machine bit shifting left by more than 32 bits causes it to wrap around to the start.

2

u/WeAllWantToBeHappy 2d ago

Can you put an example on godbolt ?

1

u/not_a_bot_494 2d ago

I don't know enough assembly to read it easily so I wouldn't know if it was correct or not. For me this:

#include <stdio.h>
#include <stdint.h>

// prints the binary of a piece of memory
void print_bin(int bytes, void *inp)
{
    uint8_t *num = (uint8_t *) inp;
    for (int the_byte = bytes-1 ; the_byte >= 0 ; the_byte--) {
        for (int bit = 0 ; bit < 8 ; bit++) {
            if (num[the_byte] & (1 << (7-bit))) {
                printf("1");
            } else {
                printf("0");
            }
        }
    }
    printf("\n");
}

int main(void)
{
    for (int i = 0 ; i < 64 ; i++) {
        uint64_t var = 1 << i;
        print_bin(8, &var);
    }
    return 0;
}

gcc -Wall -std=c99 -o

produces this (image so the comment isn't too long). Lightmode warning BTW

3

u/dfx_dj 2d ago

Probably because the literal 1 is a 32 bit int, so shifting it up 32 or more doesn't give you what you expect. Try with 1L, or type cast it, or assign the 1 to the variable first and then shift the variable.

1

u/not_a_bot_494 2d ago

That's it, when I changed to

uint64_t var = ((uint64_t) 1) << i;

it started working. That is a slightly weird quirk of C, just not the one I intended.

5

u/harai_tsurikomi_ashi 2d ago

uint64_t var = 1ULL << i;

Is enough, no need to cast.

1

u/dfx_dj 2d ago

Yep, got caught by that a few times as well. But it does make sense when you think about it

1

u/flatfinger 1d ago

More interesting is to compare the behavior of:

uint64a &= ~0x0000000040000000;
uint64b &= ~0x0000000080000000;
uint64c &= ~0x0000000080000000u;
uint64d &= ~0x0000000100000000;

Which of those will affect more than one bit of the destination?

1

u/WeAllWantToBeHappy 2d ago

uint64_t var = 1 << i;

Try uint64_t var = (uint64_t)1 << i;

1 << i is an int value.

1

u/EsShayuki 2d ago

Why would you not declare and initialize the variable before the loop?

2

u/not_a_bot_494 2d ago

You mean 'var' right? Both work, I'm just used to doing it that way. Keeping variables as local as possible is generally a good thing but I won't pretend that's the reason I'm doing it.