First, some standard language:
6.3.2.1 Lvalues, arrays, and function designators
...
2 Except when it is the operand of the sizeof operator, the _Alignof operator, the
unary & operator, the ++ operator, the -- operator, or the left operand of the . operator
or an assignment operator, an lvalue that does not have array type is converted to the
value stored in the designated object (and is no longer an lvalue); this is called lvalue
conversion. If the lvalue has qualified type, the value has the unqualified version of the
type of the lvalue; additionally, if the lvalue has atomic type, the value has the non-atomic
version of the type of the lvalue; otherwise, the value has the type of the lvalue. If the
lvalue has an incomplete type and does not have array type, the behavior is undefined. If
the lvalue designates an object of automatic storage duration that could have been
declared with the register storage class (never had its address taken), and that object
is uninitialized (not declared with an initializer and no assignment to it has been
performed prior to use), the behavior is undefined.
...
6.3.2.3 Pointers
...
2 For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to
the q-qualified version of the type; the values stored in the original and converted pointers
shall compare equal.
...
6.5.16.1 Simple assignment
Constraints
1 One of the following shall hold:112)
...
— the left operand has atomic, qualified, or unqualified pointer type, and (considering
the type the left operand would have after lvalue conversion) both operands are
pointers
to qualified or unqualified versions of compatible types, and the type pointed
to by the left
has all the qualifiers of the type pointed to by the right;
...
112) The asymmetric appearance of these constraints with respect to type qualifiers is due to the conversion
(specified in 6.3.2.1) that changes lvalues to ‘‘the value of the expression’’ and thus removes any type
qualifiers that were applied to the type category of the expression (for example, it removes const but
not volatile from the type int volatile * const).
C 2011 Online Draft
So you can take the address of a non-const object and assign it to a const pointer. You can write a new value directly to j, but you can't write a new value to *p, even though j and *p designate the same object.
This is a useful and desirable feature. A common use case is to declare pointer arguments in functions to be const as a promise that the function will not attempt to update the pointed-to object, such as in the strcat function:
char *strcat( char * restrict s1, const char * restrict s2 );
If I write something like
char foo[10] = "foo";
char bar[] = "bar";
strcat( foo, bar );
Even though bar is not const, strcat cannot write to it through the *s2 argument.
But the constraint is asymmetrical - you cannot assign the address of a const object to a non-const pointer.
In general, you can assign from a less restrictive type to a more restrictive type, but not the other way around.