11

A couple of GCC versions ago, I could do neat things like this:

$ objcopy -I binary -O elf64-x86-64 -B i386 foo.png foo.png.o

... coupled by the following in C, as an example with SDL image loading:

extern void _binary_foo_png_start;
extern void _binary_foo_png_start;
SDL_Surface *image = IMG_Load_RW(SDL_RWFromMem(&_binary_foo_png_start, &_binary_foo_png_end));

Then I would link foo.png.o together with the object file from the C file and get an executable which neatly contained foo.png.

These days, I can still do that, but GCC warns me about it:

foo.c:57:19: warning: taking address of expression of type ‘void’
foo.c:57:44: warning: taking address of expression of type ‘void’

Clearly it still works, and as far as I can tell, it really does what it's supposed to. The symbols themselves have no well defined type, and therefore it seems fitting to declare them as void. I mean, sure, I could just as well give them any other arbitrary type and it would still work just as well seeing as how I just want their address anyway, but declaring them void seemed nicer than just making up some type.

So why has GCC suddenly decided to start warning me about this? Is there some other preferred way that this should be done?

Dolda2000
  • 25,216
  • 4
  • 51
  • 92
  • 4
    Perhaps, but having my output shock full of these will squelch meaningful warnings. :) – Dolda2000 Dec 03 '14 at 04:00
  • 1
    Not to mention, of course, that it indicates that GCC suggests against doing this, and I'd like to know why that would be suggested. – Dolda2000 Dec 03 '14 at 04:06
  • It can warn about anything it likes. It could be a bug, or it could be because the developers felt it was justified. –  Dec 03 '14 at 04:11
  • 1
    This is mother nature's way of telling you to avoid non-standard GCC conctructs. You want addresses of some bytes, so giving them the `char` type is not entirely arbitrary. – n. m. could be an AI Dec 03 '14 at 04:16
  • @n.m.: According to [this answer](http://stackoverflow.com/a/10039482/134252), this is not a non-standard GCC construct. – Dolda2000 Dec 03 '14 at 04:28
  • You cannot write a valid C program that includes your code. – n. m. could be an AI Dec 03 '14 at 05:01
  • @n.m.: The answer I linked seems to indicate that you can. – Dolda2000 Dec 03 '14 at 05:02
  • It does not. You need to define your extern variables somewhere and you cannot do that in C. – n. m. could be an AI Dec 03 '14 at 05:14
  • @n.m.: It is true, of course, that you cannot define that symbol in C, but neither do you need to. You can define it either in some other language, or do as I did with `objcopy`. This still appears as a valid way to refer to such an external symbol from within a C object file. I don't think the entire program has to be written in C for one file to be consider valid C in itself. – Dolda2000 Dec 03 '14 at 05:56
  • The C standard only governs C programs, not other languages and not objcopy. It also only governs complete programs, not separate TUs. If you cannot demonstrate a complete C program that uses your construct, you are not using C as defined by the standard. – n. m. could be an AI Dec 03 '14 at 06:04
  • @n.m.: If you're going to such extremes, I would argue the discussion is quite pointless, because I'm not particularly interested in writing pure ISO C with no system, architecture or otherwise external dependencies. GCC certainly does not warn me when linking against functions doing Linux syscalls, even though those cannot be expressed in pure C either. It also makes sense, I would argue, that a symbol can have an address but no size, in which case `void` would make sense. And regardless of any of this, the construct *itself* appears to be valid C. – Dolda2000 Dec 03 '14 at 06:15
  • 1
    If you are only interested in practical programming, declare your symbols as `char[]`, which is valid, intuitive, and warning-free, and move on. If you want to know which parts of the standard you have violated, here's another one: "Each declarator declares one identifier, and asserts that when an operand of the same form as the declarator appears in an expression, it designates a function or object with the scope, storage duration, and type indicated by the declaration specifiers". There are no objects of type void, so you should not declare one. – n. m. could be an AI Dec 03 '14 at 06:51
  • Here are some other standard fragments to consider: "An lvalue is an expression (with an object type other than void) that potentially designates an object", and "The (nonexistent) value of a void expression (an expression that has type void) shall not be used in any way". – n. m. could be an AI Dec 03 '14 at 07:33
  • Aside, it looks like `SDL_RWFromMem` accepts the size as the second argument, not the end pointer. – n. m. could be an AI Dec 03 '14 at 08:26
  • 3
    Can you not use `extern char[] _binary_foo_png` etc? – abligh Dec 03 '14 at 09:48
  • Identifiers starting with an underscore and a lowercase letter are reserved to the implementation for use at file scope. – Keith Thompson Dec 03 '14 at 15:55

3 Answers3

6

It appears that at least the C11 standard disallows this:

6.3.2.1/1 An lvalue is an expression (with an object type other than void) that potentially designates an object.

If your expression is not an lvalue, you cannot take its address.

Validity of the declaration

extern void _binary_foo_png_start;

is questionable, because it arguably does not declare an object (an object cannot have type void). Two out of four C compilers I have tried accept it though. One of these compilers accepts &_binary_foo_png_start. A bug is filed.

On a historic note, it seems that it once was the intent to allow such constructs (which may explain why Gcc used to accept it) some similar discussion can be found in DR 12. Keep in mind that relevant definitions such as lvalue differ in C90, C99 and C11.

mafso
  • 5,433
  • 2
  • 19
  • 40
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • For the declaration `extern void _binary_foo_png_start;` itself, I'm not sure if it is only undefined behaviour (as there cannot be a definition) or if a diagnostic is required (I cannot find a constraint covering this). – mafso Dec 03 '14 at 15:55
  • 3
    Looking back at this, I notice, even, that your (quite interesting) link to DR 12 recommends using `extern const void` instead of simply `extern void`, which GCC actually does not warn for. – Dolda2000 Jan 25 '15 at 07:48
  • 3
    @Dolda2000 -- `extern const void` works perfectly fine with gcc 4.9.3 (gcc-arm-none-eabi-4_9-2015q3) while `extern void` generates warning. – Dmitry Oct 05 '18 at 18:23
  • 1
    @Dmitry: thank you for that tip! It was just what I needed. – TonyK Nov 25 '18 at 15:59
  • To whom it may concern: Per C11, 6.3.2.1p1 "An lvalue is an expression (with an object type other than void) that potentially designates an object". Hence, object of type ` void` (e.g. `const void`) is lvalue. – pmor Jan 24 '23 at 12:27
  • 1
    @pmor That's what the standard says, but IMHO it's not plausible that the authors of the standard intended to disallow lvalues of type `void` but allow lvalues of type `const void`. – Keith Thompson Jan 24 '23 at 21:44
3

From ISO/IEC9899:

6.3.2.2 void

1 The (nonexistent) value of a void expression (an expression that has type void) shall not be used in any way, and implicit or explicit conversions (except to void) shall not be applied to such an expression. If an expression of any other type is evaluated as a void expression, its value or designator is discarded. (A void expression is evaluated for its side effects.)

So to your question why they started warning on it:

Because they started to be able to detect this invalid usage of void.

Community
  • 1
  • 1
dhein
  • 6,431
  • 4
  • 42
  • 74
2

The C99 specification guarantees that char has sizeof(char) == 1 (6.5.3.4) which also states "The sizeof operator yields the size (in bytes) of its operand" - thus char can be used as a type to represent bytes.

Given that PNG images are also arranged in bytes, it then follows that you should use char (or related: char* or arrays-of) to represent arbitrary binary data that is arranged in bytes (e.g. PNG images).

Thus, I'd change your void _binary_foo_png_start; to char _binary_foo_png_start and maybe add a typedef char byte; statement to a shared header file.

To elaborate a little: a "byte" is the smallest directly addressable unit in memory, a byte is not guaranteed to be 8 bits (an octet), it could be larger - however if a byte was more than 8 bits it can be expected that in data-interchange scenarios that imported data would simply have "empty bits", rather than the data being re-packed along new bit-level boundaries (so 10 bytes of data from an 8-bit byte computer would still occupy 10 bytes on a 10-bit machine (but use 100 bits rather than 80).

Dai
  • 141,631
  • 28
  • 261
  • 374
  • Sure, but by that argument, `memcpy` and its likes could also just as well take `char *` arguments rather than `void *`, since all memory is bytes. It just seemed very nice to be able to declare symbols that should never be used directly `void`. – Dolda2000 Dec 03 '14 at 04:10
  • 1
    @Dolda2000 `memcpy` uses `void*` because C doesn't have operator overloading, it's so you can use `memcpy` with `short*` or `unsigned long long int*` without needing the CRT to have `memcpyint`, `memcpylong`, `memcpychar` etc. – Dai Dec 03 '14 at 04:14
  • @rowan.G are you replying to me or Dolda? you can use `memcpy` without any casts. – Dai Dec 03 '14 at 04:43
  • @Dai to dolda, you wouldnt be able to use it his way without casts – rowan.G Dec 03 '14 at 05:42