using "!!" in "c"

K

Keith Thompson

August, not Lew. Sorry.
So you haven't heard about it? It's called the ironic statement
terminator. It's mostly used by expert C programmers writing programs
intended to be read by other experts. For instance

x = 5;-)

adds some interesting uncertainty to the statement `x = 5;'. These
people find the preciseness of the latter statement boring. The only
way to be sure of what an ironic statement really means is to get to
know the author really well.

Just as "!!" is effectively a double-negative operator, yielding the
same logical result as a single-positive operator, ";-)" is a
double-positive operator (pronounced "Yeah, right!"), yielding a
positively ambiguous result. The behavior is not merely undefined;
it's both ill-defined and ill-mannered.
 
C

Chuck F.

Keith said:
.... snip ...

Just as "!!" is effectively a double-negative operator, yielding
the same logical result as a single-positive operator, ";-)" is
a double-positive operator (pronounced "Yeah, right!"), yielding
a positively ambiguous result. The behavior is not merely
undefined; it's both ill-defined and ill-mannered.

You are confusing things. There are the ambiguity operators :)
and ;-), and the disgust operators ;-( and :-(, just to name a few
classifications. Disgust normally takes precedence.

Don't forget the inverses: (-: (-; )-: )-;
or the equivalents for squares: :-] ;-] :-[ ;-[

--
"If you want to post a followup via groups.google.com, don't use
the broken "Reply" link at the bottom of the article. Click on
"show options" at the top of the article, then click on the
"Reply" at the bottom of the article headers." - Keith Thompson
More details at: <http://cfaj.freeshell.org/google/>
 
C

Chuck F.

Chris said:
.... snip ...

C distinguishes clearly between "input" and "output" logical
values for its logical operators (!, ||, &&): on input, any
nonzero value is true and zero is false, but on output, true is
exactly 1.

Note that not all "apparently logical" functions are necessarily
logical functions, e.g., isspace() and so on from <ctype.h> do
not necessarily produce only 0-or-1.

See also <http://www.c-faq.com/bool/bool2.html>.

Nor does strcmp(), another trap. The elusiveness and low
visibility of ! is a reason, to me, to #include <iso646.h> and use
the word "not".

--
"If you want to post a followup via groups.google.com, don't use
the broken "Reply" link at the bottom of the article. Click on
"show options" at the top of the article, then click on the
"Reply" at the bottom of the article headers." - Keith Thompson
More details at: <http://cfaj.freeshell.org/google/>
 
E

Emmanuel Delahaye

(e-mail address removed) a écrit :
What does "!!" operator do <... ?

It's a combination of 2 unary not-operator. The type returned by the
not-operator is int and the value is 0 or 1.

It acts like the 'binary-converter-operator'. It converts 0 to a 0 and
non-0 to 1.

#include <stdio.h>

int main (void)
{
printf("%d\n", !!0); /* 0 */
printf("%d\n", !!123); /* 1 */

return 0;
}
 
E

Emmanuel Delahaye

Lew Pitcher a écrit :
IIUC, ragnauth.cr /meant/ to say

! is "not" operator.

as in
! is the operator for "logical not"

Yes, even I, had understood ! Noticed the smiley ?
 
K

Keith Thompson

Chuck F. said:
Nor does strcmp(), another trap. The elusiveness and low
visibility of ! is a reason, to me, to #include <iso646.h> and use
the word "not".

I don't find strcmp() to be much of a trap because I don't think of
its results as boolean. I know a lot of programmers write a string
equality operation as (!strcmp(s1, s2)); I prefer the more explicit
(strcmp(s1, s2) == 0).
 
E

Emmanuel Delahaye

Chuck F. a écrit :
You are confusing things. There are the ambiguity operators :) and
;-), and the disgust operators ;-( and :-(, just to name a few
classifications. Disgust normally takes precedence.

Don't forget the inverses: (-: (-; )-: )-;
or the equivalents for squares: :-] ;-] :-[ ;-[

Thanks for having tested ThunderBird's smiley interpretor.

These ones are not interpreted:

;-( (-: (-; )-: )-; :-] ;-] ;-[
 
P

pete

Keith Thompson wrote:
I don't find strcmp() to be much of a trap because I don't think of
its results as boolean.

strcmp is more similar to a compar function,
than it is to a boolean.
I know a lot of programmers write a string
equality operation as (!strcmp(s1, s2)); I prefer the more explicit
(strcmp(s1, s2) == 0).

So would I.

For pseudoboolean expressions like isspace(c),
I prefer to write
if (isspace(c))
or
if (!isspace(c))

For most other conditional expressions,
I prefer to compare explicitly to zero.
 
R

Richard Heathfield

Keith Thompson said:
Just as "!!" is effectively a double-negative operator, yielding the
same logical result as a single-positive operator,

....but ! necessarily the same numerical result.
 
P

pemo

Keith Thompson said:
So is (value, 0) (i.e., a comma operator). It turns a scalar value
into 1 or 0. It just happens to be 0.



What's the point here?


!! maps 0 to 0, and any non-zero value to 1, for any scalar operand.



It will output some implementation-specific sequence of printing
characters. The sequence may or may not look like a number.

Can you explain this comment too? Is it an allusion to some obscure
minutiae in the std, like "In reality, an implementation is free to encode
an address such that it is displayed in egyptian hieroglyphics"?


That invokes undefined behavior.

"boo" yields a char* value. The "!" operator yields 1 if its operand
compares equal to 0, 0 otherwise. In this case, it compares the char*
value to a null pointer value. Since "boo" cannot be a null pointer,
!"boo" is guaranteed to yield 0. Applying "!" again yields the int
value 1.

So the above is equivalent to

printf("%p\n", (void*)1);

The conversion of the int value 1 to void* may yield a trap
representation; passing this to printf() (or doing anything else with
it) invokes undefined behavior.


Interesting, so something like this is basically illegal/non-portable now?

void (* n)(void) = (void(*)(void))42;

n();

Years ago I used to use something like the above for invoking interrupt
routines.
 
J

Jordan Abel

Interesting, so something like this is basically illegal/non-portable now?

void (* n)(void) = (void(*)(void))42;

n();

Years ago I used to use something like the above for invoking interrupt
routines.

He's getting a bit ahead of himself - it is not undefined, it's
implementation-defined. But it's _always_ been non-portable - what's an
interrupt routine?
 
P

pemo

Jordan Abel said:
He's getting a bit ahead of himself - it is not undefined, it's
implementation-defined. But it's _always_ been non-portable - what's an
interrupt routine?

Interrupt routine. A routine (usually held in ROM) that is normally
[caused] to be invoked by some hardware 'event' [or 'interrupt'] - e.g.,
when the hardware clocks ticks, a hardware interrupt is triggered so that
the OS etc can keep track of the current time, and the fact that the time
has just changed, i.e., this saves the OS from polling here. The location
of the routine that need calling when these events occur were held in a
table at a fixed location. So, if you were writing some lowish level code
that might want to interact with, say, the harddrive controller, it was
often nice to do at least some part of this via the harddrive's documented
interrupt interface.

So, you'd often have to fetch an interrupt routine's address, and then call
it ...

typically, it looked something like this:

typedef Interrupts int; // Used as a function pointer 'value' for any
interrupt routine.

Interrupts * inttab;

// Base address of int table. inttab[index] yields index interrupt vector.
The actual addresses are already there - BIOS!
//
inttab = (Interrupts *)0; // 0 = base address on int table. inttab[x]
yields address of x's interrupt vector's service routine.

....

// Trigger int 10

void(*n)();

// Get int 10's service routine address.

n = (void)(*)())inttab[10];

// Invoke int 10.

n();
 
F

Flash Gordon

pemo said:
Interrupt routine. A routine (usually held in ROM) that is normally

I'm sure Jordan, Keith and most of the other regulars know what an ISR
is. It does not change the fact that what you are suggesting is
non-portable.

Lets take the issue of where the interrupt vector table is. I've used
systems with it at the bottom of memory next to the reset vector and
therefore in ROM, I've used systems with it at the bottom of memory with
the reset vector at the tom, so the interrupt vector table was in RAM,
I've used systems where the location of the interrupt vector table is
configurable, so it could be anywhere! That show your hard coded address
is non-portable.

I've also used sytems where the interupt vector table does *not* contain
some kind of jump/call instruction, so you would actually need another
level of indirection on those systems.

table at a fixed location. So, if you were writing some lowish level code
that might want to interact with, say, the harddrive controller, it was
often nice to do at least some part of this via the harddrive's documented
interrupt interface.

Not something I've needed to do, but i'll accept it's been useful to
you. However, useful does not mean portable. So now lets deal with the
issue of calling an ISR. I've used systems where the return call/return
mechanism for an ISR was not the same as the mechanism for a normal
function. So to call an ISR you would actually have to use a CALLI
instruction, when the compiler would also emit a CALL instruction. So,
even having used some magic to get the address of the ISR you *still*
can't call it from C on a system like this and if you try you will crash
your program even if the ISR does nothing other than return.
So, you'd often have to fetch an interrupt routine's address, and then call
it ...

<snip non-portable code>

Which you can't do portably. This is not to say you can't do it at all,
on some systems you can.

The conversion from integer to pointer is implementation defined (apart
from an integer *constant* of 0) and could generate a trap
representation. If it generates a trap representation then evaluating it
(which you have to do to assign it to a pointer unless you cheat and
do it via an unsigned char pointer) invokes undefined behaviour. Even if
you manage to get past that, you still leave standard C a long way
behind when you try calling it.
 
F

Flash Gordon

pemo said:
What's the point here?

I believe that Keith was just pointing out that your specification of
what !! does was incomplete. What he wrote met your specification but
would generally be pointless in real code (there are potentially some
exceptions) and does not do what !! does.
Can you explain this comment too? Is it an allusion to some obscure
minutiae in the std, like "In reality, an implementation is free to encode
an address such that it is displayed in egyptian hieroglyphics"?

One very common format of output is something like "1234:5678", another
is something like "0x12345678". You will note that the first does not
look like a number because of the colon. Another common output is
something like "(null)" which looks even less like a number. What %p
prints can be in any format as long as it can be read by a %p in a scanf
on that implementation and yield the original pointer. Frequently
(though it is not required) it is whatever format is generally used in
printed material for addresses, and when an address on a machine is not
a simple thing (e.g. segment and offset as supported by x86 processors)
that is quite likely to mean the output won't look like a simple
integer. Egyptian hieroglyphics could legally be used as long they were
part of the execution character set and scanf processed them appropriately.

Well, it only conditionally invokes undefined behaviour. If it happens
not to produce a trap representation then it is only implementation
defined behaviour IIRC. Of course, it should still generally be treated
as undefined behaviour.
Interesting, so something like this is basically illegal/non-portable now?

void (* n)(void) = (void(*)(void))42;

n();

Years ago I used to use something like the above for invoking interrupt
routines.

What you show the above has *always* been non-portable.
 
P

pemo

What's the point here?
I believe that Keith was just pointing out that your specification of
what !! does was incomplete. What he wrote met your specification but
would generally be pointless in real code (there are potentially some
exceptions) and does not do what !! does.


If that *IS* the case, it seems um, rather unnecessarily pedantic - I
believe that what I said was clear enough - and corrrect in as far as it
went: and I also didn't say it was the only way to do something .... so why
point out alternatives? Maybe it's necessary to produce litanies containing
alternative possibilities for each non-exhaustive statement ever made here
sometimes?

Can you explain this comment too? Is it an allusion to some obscure
minutiae in the std, like "In reality, an implementation is free to encode
an address such that it is displayed in egyptian hieroglyphics"?
One very common format of output is something like "1234:5678", another
is something like "0x12345678". <snip>


Ah, so it was as I expected - an allusion to some obscure minutiae [well, it
is isn't it - letter-of-the-law-correct-or-not] that seems picky and
generally non-constructive. The two examples you gave look like numbers to
me. And I would hazard a guess that most every compiler outputs something
similar - even if it doesn't *have to*. So, saying that a compiler *could*
output this in a braille encoding of the morse-code interpretation of
egyptian hieroglyphics is kinda like saying that there is a remote
possibility that you *could* pick up a book which is written in English -
but that you can't read - because the author decided to choose a remote
vocabulary and to 'encode' his/her prose in such an impenetrable way as it
make it more or less impossible. Both situations *could* exist, but both
are, in reality, unlikely to - so what's the point in making a noise about
it?

What you show the above has *always* been non-portable.


Yes [I know!], my V.POOR wording - the question ought to have been ... is
the code illegal, without going through the unsigned char * route?
 
F

Flash Gordon

pemo wrote:

Please quote properly and leave in the attribution lines. If OE gives
you problems you can try searching for and using QuoteFix. You were
quoting properly previously and this time I'm going to fix it.
If that *IS* the case, it seems um, rather unnecessarily pedantic -

This is CLC, people *are* pedantic here!
> I
believe that what I said was clear enough - and corrrect in as far as it
went: and I also didn't say it was the only way to do something .... so why
point out alternatives? Maybe it's necessary to produce litanies containing
alternative possibilities for each non-exhaustive statement ever made here
sometimes?

Pointing out holes in what people say is not uncommon here.
One very common format of output is something like "1234:5678", another
is something like "0x12345678". <snip>

Ah, so it was as I expected - an allusion to some obscure minutiae [well, it

You think x86 based machines are obscure minutiae?
is isn't it - letter-of-the-law-correct-or-not] that seems picky and
generally non-constructive. The two examples you gave look like numbers to
me. And I would hazard a guess that most every compiler outputs something

Numbers do not have colons in the middle, at least not in any
representation I've come across and "(null)" is definitely *not* a
number which was another example I gave but you snipped.

make it more or less impossible. Both situations *could* exist, but both
are, in reality, unlikely to - so what's the point in making a noise about
it?

Implementations which print out the address with a : in the middle are
very real, and the processor for which such implementations were written
are still in current use.
What you show the above has *always* been non-portable.

Yes [I know!], my V.POOR wording - the question ought to have been ... is
the code illegal, without going through the unsigned char * route?

Subject to me missing errors what you are suggesting is perfectly
acceptable syntactically and the compiler is not required to produce a
diagnostic. So in that sense it is "legal". However, since it invokes
undefined behaviour it is in another sense "illegal". So take your pick.
 
K

Keith Thompson

pemo said:
What's the point here?



Can you explain this comment too? Is it an allusion to some obscure
minutiae in the std, like "In reality, an implementation is free to encode
an address such that it is displayed in egyptian hieroglyphics"?

I was going to post an explanation of what I meant, but Flash Gordon's
response explains it at least as well as I would have.
 
K

Keith Thompson

pemo said:
what !! does was incomplete. What he wrote met your specification but
would generally be pointless in real code (there are potentially some
exceptions) and does not do what !! does.


If that *IS* the case, it seems um, rather unnecessarily pedantic - I
believe that what I said was clear enough - and corrrect in as far as it
went: and I also didn't say it was the only way to do something .... so why
point out alternatives? Maybe it's necessary to produce litanies containing
alternative possibilities for each non-exhaustive statement ever made here
sometimes?

(A number of attributions were snipped. I wrote the text starting
with "So is (value, 0)", pemo wrote the text starting with "What's the
point here?", and Flash Gordon wrote the text starting with "I believe
that Keith was". Please leave attributions in place. Thanks.)

What you wrote was:

!! is a good way of turning a scalar value into 1 or 0.

That's not a useful specification unless you also specify *when*
it yields 0 and when it yields 1. By itself, it doesn't provide any
clue of why you'd want to turn a scalar value into 1 or 0. I was just
emphasizing the fact that !! maps 0 to 0 and all non-zero mappings
to 1. There are many many other possible mappings.

Whether I was being overly pedantic is a matter of opinion, of course.
I likely wouldn't have bothered mentioning it if I weren't commenting
on other things in your article.
Can you explain this comment too? Is it an allusion to some obscure
minutiae in the std, like "In reality, an implementation is free to encode
an address such that it is displayed in egyptian hieroglyphics"?
One very common format of output is something like "1234:5678", another
is something like "0x12345678". <snip>

Ah, so it was as I expected - an allusion to some obscure minutiae [well, it
is isn't it - letter-of-the-law-correct-or-not] that seems picky and
generally non-constructive. The two examples you gave look like numbers to
me. And I would hazard a guess that most every compiler outputs something
similar - even if it doesn't *have to*. So, saying that a compiler *could*
output this in a braille encoding of the morse-code interpretation of
egyptian hieroglyphics is kinda like saying that there is a remote
possibility that you *could* pick up a book which is written in English -
but that you can't read - because the author decided to choose a remote
vocabulary and to 'encode' his/her prose in such an impenetrable way as it
make it more or less impossible. Both situations *could* exist, but both
are, in reality, unlikely to - so what's the point in making a noise about
it?

No, this is not "some obscure minutiae", this is part of a basic
understanding of the language.

Here's what the standard says about the "%p" format for the *printf()
functions:

The argument shall be a pointer to void. The value of the pointer
is converted to a sequence of printing characters, in an
implementation-defined manner.

There's also some wording for the *scanf() functions saying that it
can read the same format produced by *printf().

There is no implication that the output looks anything like a number,
and portable code cannot depend on any particular output format. It
happens that most implementations use something that looks like a
numeric format, possibly with extra decorations (leading "0x",
inserted ':', etc.). One system I just tried prints a hexadecimal
string *without* a leading "0x", such as "ffbff374", which happens to
be a valid identifier.

It's tempting to think that addresses are just another kind of number,
but that's a dangerous conceptual trap. They're often implemented
that way, but a C address or pointer value should be thought of as an
opaque entity that can be used to access other entities.

In any case, the example you chose didn't illustrate the point you
were making very well.

That's legal but non-portable. The conversion of the int value 42 to
a pointer-to-function type yields an implementation-defined result; it
could be a trap representation. If there doesn't happen to be a
function of the proper type whose address is whatever you get by
converting the int value 42, Bad Things Will Happen.

Of course this is a perfectly legitimate thing to do as long as you
really know what's going on and you're aware that it's non-portable.

On some systems, converting an integer to a pointer-to-function might
*never* yield a meaningful value. (For example, I think the AS/400
has a very exotic format for function pointers.)
 
P

pemo

Flash Gordon said:
pemo wrote:

Please quote properly and leave in the attribution lines. If OE gives you
problems you can try searching for and using QuoteFix. You were quoting
properly previously and this time I'm going to fix it.


Sorry, I'm a terrible poster, and I'll look at QuoteFix.

If that *IS* the case, it seems um, rather unnecessarily pedantic -

This is CLC, people *are* pedantic here!
I
believe that what I said was clear enough - and corrrect in as far as it
went: and I also didn't say it was the only way to do something .... so
why point out alternatives? Maybe it's necessary to produce litanies
containing alternative possibilities for each non-exhaustive statement
ever made here sometimes?

Pointing out holes in what people say is not uncommon here.
printf("%p\n", (void *)"boo");

will output some value that's not 0, and very unlikely to be 1, e.g.,
0040300
It will output some implementation-specific sequence of printing
characters. The sequence may or may not look like a number.
Can you explain this comment too? Is it an allusion to some obscure
minutiae in the std, like "In reality, an implementation is free to
encode an address such that it is displayed in egyptian hieroglyphics"?
One very common format of output is something like "1234:5678", another
is something like "0x12345678". <snip>

Ah, so it was as I expected - an allusion to some obscure minutiae [well,
it


You think x86 based machines are obscure minutiae?


No, not that! [see also below], rather that the comment I thought
uneccesary, *was* (IMHO)

It was essentially 'pulling me up' for stating that the output *might* be
0040300 [or 0123456, or 123, or 34234234234234234] - and that I didn't add
some, 'oh, and it might just be in output in hieroglyphics on your
implementation'!

Maybe the point was made so that some 'knumb nuts' didn't get the wrong end
of the stick, or maybe it was made just to remind someone [who I don't know]
that the output might actually be different from the example - maybe it was
just made to personally annoy me - I dunno, but it pissed me off anyway!


is isn't it - letter-of-the-law-correct-or-not] that seems picky and
generally non-constructive. The two examples you gave look like numbers
to me. And I would hazard a guess that most every compiler outputs
something

Numbers do not have colons in the middle, at least not in any
representation I've come across and "(null)" is definitely *not* a number
which was another example I gave but you snipped.


Yes, my phd wasn't wasted - and I've noticed that numbers don't have numbers
in the middle **using standard notation [see below]** - but they *can* have
camels in the middle if *you* want them to - and if you want to use such
notation personally - as some legal characterisation of some abstract scalar
view of the world. Which comes back to a problem with this ... if the
output can be anything, well, we ought to always include standard
disclaimers about *any* claim we make here regarding *any* output.
Ridiculus - so, lets point it out when it's properly pertinent, and not just
when we just feel the urge to bump our gums, and add some white noise into
the system?
Numbers do not have colons in the middle

Actually, that's rubbish, as I used them for many years with segmented
architectures - such numbers then meant as much to me - as in they had an
intuitive meaning for me (as 123 does now/then). This last bit is a little
like the 'pulling up' I had

'Actually the output of pemo's mathematics will be a pemo
implementation-specific sequence of printing
characters [maybe]. The sequence may or may not look like any number you're
used to'

Irksome eh?
 
J

Jordan Abel

Subject to me missing errors what you are suggesting is perfectly
acceptable syntactically and the compiler is not required to produce a
diagnostic. So in that sense it is "legal". However, since it invokes
undefined behaviour it is in another sense "illegal". So take your pick.

It does not. The conversion is implementation-defined. While the program
still cannot be strictly conforming, there is no undefined behavior
involved.

If I do this on my system, I will get a segmentation fault. Furthermore,
it will happen when the pointer is dereferenced, not when it is
assigned. While there is no documentation that actually says this _as
such_, all parts of the chain of events from the int/pointer conversion
to the mapping of the virtual memory page containing address 42 to the
conditions under which the signal in question will be raised are
documented _somewhere_, and it can be inferred from these facts without
ever attempting to enter, compile, or run the code.

I believe that the implementation is free to leave certain
implementation-defined aspects undefined, such as if the address
0x80484f8 were instead used. This happens to, in my test program, be the
address where the symbol 'main' is mapped. Since the address is in a
text page, i can attempt to call it and arbitrary code may be run, in
this case, a recursive call to main(). However, none of this is defined
by my implementation. However, from the point of view of the C standard,
implementation-undefined behavior is still implementation-defined.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top