3

Consider the following code:

struct ns_test{
    char *str;
};

struct ns_test *ns_test_alloc(char *str){
    struct ns_test *nt = malloc(sizeof(*nt));
    nt->str = str;
    return nt;
}

const char *ns_test_get_str(struct ns_test *tst){
    return tst->str;
}

void ns_test_release(struct ns_test* tst){
    free(tst);
}

void ns_test_set_char(struct ns_test *tst, size_t i, char c){
    tst->str[i] = c;
}

int main(void){
    char arr[] = "1234567890";
    struct ns_test *ns_test_ptr = ns_test_alloc(arr);
    const char *str = ns_test_get_str(ns_test_ptr);
    printf("%s\n", str); //1234567890
    ns_test_set_char(ns_test_ptr, 4, 'a');
    printf("%s\n", str); //1234a67890
}

The question is: Is the behavior of the code undefined?

I think it is.

The Standard specifies at 6.7.3(p6):

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

So to avoid this sort of UB we need const char *ns_test_get_str(struct ns_test *tst) to copy the char *str instead. But the main point was to avoid the copying and restrict the modification to the only void ns_test_set_char(struct ns_test *tst, size_t i, char c) which may do some sanity checks or something else prior.

6
  • 3
    But the malloced array never was const. Only the pointer had this type . There is no UB here as you derefernence another pointer. Commented May 8, 2019 at 18:21
  • @P__J__ but the char arr[] in the main function is not malloced. Commented May 8, 2019 at 18:22
  • 1
    char arr[] isn't really important here. It's your ns_test_ptr->str that is being aliased. Commented May 8, 2019 at 18:23
  • And your question is...? Commented May 8, 2019 at 18:23
  • @ChristianGibbons So the only thing that matters is the declared type of the object. In this case ns_test_ptr->str reffers to the object with declared type char[] as char arr[] was declared. So there is no problem. Commented May 8, 2019 at 18:27

1 Answer 1

2

The key here is "an object defined with a const-qualified type". What matters is how you define (IOW, "allocate memory for") the object being pointed at. If the "object" was not "created" as const, then it does not matter how many const or non-const pointers and references you use - you can still modify it.

So,

const int i = 0;
int *p = (int*)&i;
*p = 1;

is UB.

While

int i = 0;
const int *cp = &i;
int *p = (int*) cp;
*p = 1;

is fine.

I suspect this would even work with new:

const int *cp = new const int(0);
int *p = (int*) cp;
*p = 1;

is technically UB.

It does compile w/o warning on cpp.sh, but that does not mean much.

Update: as Christian Gibbons pointed out, the language in the question is C, so the part about new operator does not apply. malloc() and friends are never const.

To expand this a little - one possible reason for writing the standard this way is to give the compiler freedom to use read-only memory for const values. In that case, writing to such locations becomes a crash or a noop. In other words, UB.

Sign up to request clarification or add additional context in comments.

1 Comment

It should be pointed out that this is a C question. I understand C and C++ can treat const rather differently.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.