9

The function returns -1.

However, what is the meaning of (void*) in the following code:

can it work without it ?

test = shmat(shm_id, NULL, 0);

if (test == (void *)-1) {
    /* handle shmat failure */
}
msc
  • 33,420
  • 29
  • 119
  • 214

2 Answers2

16

shmat has the following prototype:

void *shmat(int shmid, const void *shmaddr, int shmflg);

i.e. it returns a pointer to void, not an integer.

On error it will return the integer -1 cast to a pointer to void. From Linux manpages shmat(2):

On success, shmat() returns the address of the attached shared memory segment; on error, (void *) -1 is returned, and errno is set to indicate the cause of the error.

That is how you must do the comparison properly to check for an error return. The C11 standard says the following about the operator == in 6.5.9p5-6:

5 Otherwise, at least one operand is a pointer. If one operand is a pointer and the other is a null pointer constant, the null pointer constant is converted to the type of the pointer.

If one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void, the former is converted to the type of the latter.

6 Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.109)

That is, the standard defines behaviour for exactly 2 conversions: either one operand is a pointer to void, and the other operand is a pointer to something else; or one operand is a pointer, and the other operand is the null-pointer constant (i.e. 0, or (void*)0, or so). Since -1 without casts is neither a null-pointer constant, nor a pointer, the standard doesn't specify any behaviour for such case, thus the behaviour is undefined "by the omission of any explicit definition".

test == -1 is wrong, and test == (void *) -1 is right(ish).

As it turns out this is still a grey area. -1 is an int and on my computer, it is 32 bits. The conversion of an integer to pointer is defined by the implementation. The GCC manuals say:

A cast from pointer to integer discards most-significant bits if the pointer representation is larger than the integer type, sign-extends[2] if the pointer representation is smaller than the integer type, otherwise the bits are unchanged.

With footnote 2 saying

Future versions of GCC may zero-extend, or use a target-defined ptr_extend pattern. Do not rely on sign extension.

Thus it would mean that (void *)-1 might become incorrect too; the safer way would be to write it as (void *)(intptr_t)-1.


For some reason the (void *)0, i.e. the null pointer, is not used to signal an error condition (even though use of such a pointer in C would be erroneous standard-wise).

Community
  • 1
  • 1
  • 3
    Historically, it used to be possible to map the zero page (so that `(void *) 0` turns into a valid address at which objects can be placed. This is why `mmap` does not return `NULL` on failure. I do not know if this applies to `shmat` as well, but it could be the reason for this behavior. – Florian Weimer Jul 18 '17 at 10:04
  • @FlorianWeimer Correct, this is also possible today with linux (on some installations you might have to adjust `/proc/sys/vm/mmap_min_addr` before) – Ctx Jul 18 '17 at 10:08
  • 1
    Should it not be `test == (void *) -1L` or even `test == (void *) -1LL` on 64-bit systems? – chqrlie Jul 18 '17 at 10:30
  • @chqrlie the conversion is implementation specified... who knows :D – Antti Haapala -- Слава Україні Jul 18 '17 at 10:32
  • @chqrlie ok, added more information. – Antti Haapala -- Слава Україні Jul 18 '17 at 10:37
  • The C Standard has this language: ***6.3.2.3 Pointers** [...] An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.* – chqrlie Jul 18 '17 at 10:40
  • 1
    My point is that while GCC currently does it that way, it doesn't guarantee sign extension (in the future). – Antti Haapala -- Слава Україні Jul 18 '17 at 10:41
  • 5
    The man pages for `mmap` and `shmat` only mention `(void*)-1`, `clang` happens to implement `(void*)-1` as `(void*)(inptr_t)-1` and I suppose all Posix compliant compilers must do the same for the specification to stay consistent. Interesting corner case. – chqrlie Jul 18 '17 at 10:43
  • 1
    I suppose one could check `errno` instead of trying to decode an error state out of the returned pointer. Note that `errno` is only set on error, so you would have to explicitly clear it before calling `shmat` – Rodney Jul 18 '17 at 12:03
2

It is type casting.

The function shmat() returns a void*, so type casting is used to convert -1 (int) to the correct type (void*).

Actually, yes it could work but you'll get some warnings from the compiler.

Never ignore warnings they can hide bugs.

LotoLo
  • 327
  • 4
  • 17