...
unsigned int print(char *message, unsigned int line)
{
char *vidmem = (char *) 0xb8000;
unsigned int i= 0;
i=(line*80*2);
while(*message!=0) // 24h
{
vidmem[i]= *message;
*message++;
i++;
vidmem[i]= 0x7;
i++;
};
return(1);
};
...
Keywords include the 'Address attribute, representations (LRM 13.3),
possibly address-to-access conversions, some details from Annex C;
many text books on Ada 95 and later will explain. A compiler
will explain how it supports machine code insertions on a given
platform.
The tutorial "Accessing memory as a string" by Robert Dewar
might be useful.
The Ada archives should, I think, have examples of writing to
DOS's video buffer.
I do not have any experience about this part of Ada, but maybe I can give you a "pointer" to some bootstrapping information. Maybe you could want to use an attribute representation clause for Vidmem'Address (see Section 13.3 of the RM) . With some improvisation, I would write something like
type Vidmem_Array is array (natural range <>) of Character;
Vidmem : Vidmem_Array (0 .. Max_Size);
for Vidmem'Address use 16#000B_8000#;
Note that with this approach you need to know the maximum size of your "buffer". Otherwise, you could go through System.Address (see 13.7 and following of the RM).
As disclaimed above, I am not an expert on this, so maybe someone could want to integrate/correct/whatever the information above.
*Every* time I look at C code, I see a bug.
I think this is a bug; it should read "message++" since you want to
increment the pointer, not the pointed-to character.
> i++;
> vidmem[i]= 0x7;
> i++;
> };
>
> return(1);};
>
> ...
The proper way to achieve what you want is not to translate the
(buggy) C code into Ada; instead, use Ada to model the problem and
provide a clean abstraction. In this case, the model is that the
video memory, located at some address, is a two-dimensional array of
cells; each cell contains a character and an attribute byte.
type Cell_T is record
Char : Character;
Attribute : Interfaces.Unsigned_8;
end record;
pragma Pack (Cell_T);
type Horiz_Coordinate_T is mod 80;
type Vert_Coordinate_T is mod 25;
type Console_T is array
(Horiz_Coordinate_T, Vert_Coordinate_T) of Cell_T;
pragma Pack (Console_T);
Console : Console_T;
for Console'Address use 16#b8000#; -- the only low-level trick!
procedure Put (C : in Character;
Into_Console : in out Console_T;
At_X : in Horiz_Coordinate_T;
At_Y : in Vert_Coordinate_T) is
begin
Into_Console (At_X, At_Y) :=
(Char => C, Attribute => 16#7#);
end Put;
procedure Put (S : in String;
Into_Console : in out Console_T;
At_X : in Horiz_Coordinate_T;
At_Y : in Vert_Coordinate_T) is
X : Horiz_Coordinate_T := At_X;
Y : Vert_Coordinate_T := At_Y;
begin
for J in S'Range loop
Put (C => S (J),
Into_Console => Into_Console,
At_X => X,
At_Y => Y);
X := X + 1; -- modular arithmetic: wraps around
if X = 0 then
Y := Y + 1; -- ditto
end if;
end loop;
end Put;
Hope this helps
--
Ludovic Brenta.
Excellent, this defiantly helps.
>> unsigned int print(char *message, unsigned int line)
>> {
>> char *vidmem = (char *) 0xb8000;
>> unsigned int i= 0;
>>
>> i=(line*80*2);
>>
>> while(*message!=0) // 24h
>> {
>> vidmem[i]= *message;
>> *message++;
>
> *Every* time I look at C code, I see a bug.
> I think this is a bug; it should read "message++" since you want to
> increment the pointer, not the pointed-to character.
This is not a bug, since postfix ++ has higher precedence than *.
Indeed, *t++ = *s++; is idiomatic C.
While the result of the * operation on the pre-incremented pointer
doesn't seem to have a visible effect in this program, it still might
be intended, the intention possibly being to read the contents of
(possibly volatile) location *message, ahead of time. Who knows?
It might just as well be a c&p issue.
Warning self promotion follows:
Chapter 2 of the book "Building Parallel, Embedded, and Real-Time
Applications with Ada" by McCormick, Singhoff, and Hugues includes a
long section on low level programming with Ada. It covers both memory
mapped and port I/O architectures. We develop polling and interrupt
based device drivers. Everything is done using high level Ada
abstractions for low level features. It includes a small amount of
machine code insertion. We just turned in the final page proofs and
expect to have hard oopy by April. See http://www.cambridge.org/gb/knowledge/isbn/item5659578
John
An address clause is the usual approach. If one is going to write to such
memory, one probably doesn't care what the Ada runtime might do to initialize
it, but if one is planning to read predefined information stored there, one
probably wants to prevent any initialization through a pragma Import:
pragma Import (Ada, Vidmem);
System.Address is an implementation-defined type, and an address clause takes a
value of that type. A universal integer may or may not be acceptable. Any such
code is not portable.
Other possibilities include Ada.Storage_IO (ARM A.9) and
System.Address_To_Access_Conversions (ARM 13.7.2). Masochists (such as those who
use C-family languages by choice) might want to use address arithmetic, found in
System.Storage_Elements (ARM 13.7.1).
Note that the quoted C code will happily read from memory not part of message
and write to memory not part of vidmem; Ada will not.
--
Jeff Carter
"How'd you like to hide the egg and gurgitate
a few saucers of mocha java?"
Never Give a Sucker an Even Break
101
As others have explained, this is not an error, though the unused dereference is
wasted effort. However, if message does not contain zero, this will happily read
memory outside of message, and if the first zero encountered follows enough
bytes, it will happily write memory outside of vidmem.
> Console : Console_T;
> for Console'Address use 16#b8000#; -- the only low-level trick!
You should also add
pragma Import (Ada, Console);
to suppress any initialization code.
OK, so my hunch was wrong; there was not one but two bugs in that
line.
Funny to see that my quickly-written, untested Ada code cannot
possibly have these bugs. Proper arrays prevent reading the
terminating null character since there isn't one; the modular types
for the indexes prevent writing outside the video buffer. Once more,
praise the Lady :)
--
Ludovic Brenta.
Did I mention that *every* time I look at C code I see a bug?
Sometimes, looking twice at the same code reveals two bugs! Here you
do not check that message != NULL; the condition should be:
while (message && *message != 0)
>> {
>> vidmem[i]= *message;
>> *message++;
>
> *Every* time I look at C code, I see a bug.
> I think this is a bug; it should read "message++" since you want to
> increment the pointer, not the pointed-to character.
>
>> i++;
>> vidmem[i]= 0x7;
>> i++;
>> };
>>
>> return(1);};
--
Ludovic Brenta.
>>> unsigned int print(char *message, unsigned int line)
>>> {
>>> char *vidmem = (char *) 0xb8000;
>>> unsigned int i= 0;
>>>
>>> i=(line*80*2);
>>>
>>> while(*message!=0) // 24h
>
> Did I mention that *every* time I look at C code I see a bug?
> Sometimes, looking twice at the same code reveals two bugs! Here you
> do not check that message != NULL; the condition should be:
>
> while (message&& *message != 0)
I think you won't be convincing a C programmer by stipulating that
he has been stupid and passed a null pointer for message.
He hasn't, he has thought about his program.
And it won't help promote Ada to argue that the stupid misuse
of a C function is a property of C. You are inventing contract
breaches that are equivalent to those in the style of Ariane 4/5.
Ada's type system and range checking has not prevented
programmers and engineers making a mistake.
E.g., you'd have to show a demonstration that, typically,
arrays with index subtype excluding 16 and then accessed
with value 16 have a suitable exception handler with them.
Or you'd end up talking about programmer competence, too,
and there we are, the C folks begging the question.
CVEs, as every real C programmer knows, are caused by stupid,
incompetent C programmers who should find a different occupation.
> On 2/4/11 10:24 AM, Ludovic Brenta wrote:
>
>> Did I mention that *every* time I look at C code I see a bug?
>> Sometimes, looking twice at the same code reveals two bugs! Here you
>> do not check that message != NULL; the condition should be:
>>
>> while (message&& *message != 0)
>
> I think you won't be convincing a C programmer by stipulating that
> he has been stupid and passed a null pointer for message.
It might help to convince him that C is not a simple language.
> He hasn't, he has thought about his program.
Important is not "if", but "what."
> And it won't help promote Ada to argue that the stupid misuse
> of a C function is a property of C.
It is not a misuse, it is how C is used, the state of the art.
> You are inventing contract
> breaches that are equivalent to those in the style of Ariane 4/5.
> Ada's type system and range checking has not prevented
> programmers and engineers making a mistake.
I love this kind of logic: if you cannot prevent cancer, why would you
check the car brakes?
> CVEs, as every real C programmer knows, are caused by stupid,
> incompetent C programmers who should find a different occupation.
Nope, the fact is that probably no programmer is competent to use C.
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
Did I mention that people who criticize C are like people
who criticize grammar? Every time they do so, their own
criticism contains errors.
You make two errors here. The first is to assume without
any actual knowledge that it is possible for the argument
to be a null pointer; it is likely that the contract for
this procedure requires that it not be. The second is that
you test for the pointer being null each time through the
loop rather than just once.
I'm not sure this check for null is useful, but I'm not sure this is a valid
objection, either. message is incremented inside the loop. If the initial value
isn't null, it's still possible for it to be incremented until it rolls over and
becomes null. Of course, if that happens, it's a symptom of another error:
message does not contain a terminator byte.
--
Jeff Carter
"In the frozen land of Nador they were forced to
eat Robin's minstrels, and there was much rejoicing."
Monty Python & the Holy Grail
70
> Did I mention that people who criticize C are like people
> who criticize grammar? Every time they do so, their own
> criticism contains errors.
Right, because nobody really understands C, there is no way to reason about
C without running into errors.
> You make two errors here. The first is to assume without
> any actual knowledge that it is possible for the argument
> to be a null pointer; it is likely that the contract for
> this procedure requires that it not be. The second is that
> you test for the pointer being null each time through the
> loop rather than just once.
You just made the error of which you accused Ludovic. You assumed a
contract that the pointer cannot be modified within the loop, e.g. upon
video buffer modification.
Quality of a programming language is in great part about how close
different people could understand explicit and implied contracts when
reading the code.
C has an ISO standard which describes the language.
> You just made the error of which you accused Ludovic. You assumed a
> contract that the pointer cannot be modified within the loop, e.g. upon
> video buffer modification.
The pointer *is* modified within the loop, by incrementation.
If that pointer were situated within the video buffer itself,
the program could behave very oddly indeed, but such behavior
would almost certainly by undefined by terms of the standard
and it's unlikely that it would be "saved" by adding a check
for nullity in each iteration.
You could achieve the same oddness in Ada in the same way, by
forcing the program to place a pointer in the video buffer. In
both languages, the pointer would never be there on its own.
> Quality of a programming language is in great part about how close
> different people could understand explicit and implied contracts when
> reading the code.
The implicit contract of the subroutine is that it receives a
null-terminated string that's copied to a specified line of the
video buffer. It does no checking that the message is not null,
that the message is short enough, and that the line is within
range, so that needs to be done before the function is called.
As presented it is not possible to determine IF it is not
possible to pass a null-pointer in; unlike Ada, C has no null
exclusion and so cannot guarantee from the contract-that-is-
the-parameter-list that to be the case -- He MUST reason it
from the code itself making the argument dependent on the
context in which it is used. {For example, it could be a part
contained in a header-file whose original using-program DID
have the impossibility of passing NULL, but if re-used then
it may be the case that there is no such guarantee.
> it is likely that the contract for
> this procedure requires that it not be. The second is that
> you test for the pointer being null each time through the
> loop rather than just once.
This is a deficiency, IMO, of C and somewhat even Pascal;
In Ada, even w/o null-exclusion, we would likely say:
Procedure Some_Procedure( Message : Access String ) is
begin
if Message /= Null then
Declare
S : String Renames Message.All;
Begin
For Index in S'Range loop
-- whatever processing and display
End Loop;
End;
end if;
end Some_Procedure;
And within the procedure itself we capture the context which
would in the C-program and bind it directly to the procedure.
This becomes trivial with a not null access parameter.
> Did I mention that people who criticize C are like people
> who criticize grammar? Every time they do so, their own
> criticism contains errors.
Shouldn't that be "their own criticisms contain errors"?
-- Adam
As required by Muphry's law:
<http://en.wikipedia.org/wiki/Muphry's_law>
--
John B. Matthews
trashgod at gmail dot com
<http://sites.google.com/site/drjohnbmatthews>
I think I see the cause of the disagreement: there is no such thing as an
"implicit" contract. Either it is part of the explicit contract, or it
doesn't exist and it has to be checked somehow -- because programmers are
human and they *will* make mistakes or even be incompetent.
*Ada* is deficient in the ability to write proper contracts (improving this
is a primary driving force behind Ada 2012, after all), C is even worse in
this respect.
An implicit contract is the same as assuming something, and we all know what
"assume" does: it makes an "a**" of "you"and "me". :-)
Randy.
> You make two errors here. The first is to assume without
> any actual knowledge that it is possible for the argument
> to be a null pointer; it is likely that the contract for
> this procedure requires that it not be.
I've read that a countless time. The problem is that it is not possible
to enforce a contract in C. I've seen many bugs just for that. At some
point a developer think about a contract and some time later this is
just forgotten (even if clearly written in comment, who read comments!)
and then a crash arise. Don't tell me you've never run into such situation.
Pascal.
--
--|------------------------------------------------------
--| Pascal Obry Team-Ada Member
--| 45, rue Gabriel Peri - 78114 Magny Les Hameaux FRANCE
--|------------------------------------------------------
--| http://www.obry.net - http://v2p.fr.eu.org
--| "The best way to travel is by means of imagination"
--|
--| gpg --keyserver keys.gnupg.net --recv-key F949BD3B
That still isn't good enough unless you can guarantee that the compiler
generated code will not evaluate the second condition if the first condition
is false.
The reason is that this code will attempt to examine the contents of address
0 when message is null and on some operating systems page 0 is unmapped in
order to catch these kinds of bugs.
Simon.
--
Simon Clubley, clubley@remove_me.eisner.decus.org-Earth.UFP
Microsoft: Bringing you 1980s technology to a 21st century world
That is guaranteed in C. The C "&&" operator is short-circuiting and
corresponds to the Ada "and then". The C99 standard says "If the first
operand compares equal to 0, the second operand is not evaluated."
--
Niklas Holsti
Tidorum Ltd
niklas holsti tidorum fi
. @ .
That's good to know.
However, I've used C compilers in the past when it wasn't true so I
developed a style of testing the not null condition first in a if
statement before going into the while condition.
I suspect I am not the first person whose current coding style reflects
lessons learnt from using older compilers while those problems have
subsequently been fixed in later standards. :-)
> I suspect I am not the first person whose current coding style reflects
> lessons learnt from using older compilers while those problems have
> subsequently been fixed in later standards. :-)
The funniest C compiler I met was under Sys.V. It modified the format
string passed to printf. The string was actually restored when formatting
was done. Alas, the very same compiler stored string literals in the
read-only memory segment!
Immutable scalar arguments still ire mutable within C and C++ subprograms.
Great ideas never die...
> Immutable scalar arguments still ire mutable within C and C++ subprograms.
Scalar arguments are passed by copy, so whether they are immutable or
mutable within the function is independent on their origin (and the
original is not affected by this in any way).
Unless, of course, you have meant something else.
--
Maciej Sobczak * http://www.inspirel.com
The scalar-argument that is passed when dealing with strings/arrays
[in C & C++]
is that of the address, IIRC. While that address is itself an
"immutable scalar" the
data pointed to is not so constrained and so may be 'mutated' thereby
enabling a
perceived-immutable to be altered. {The newer standards may [or may
not] have
addressed this issue: I seem to recall a "pointer to constant" idea
which would
[I believe] behave in the expected manner if it was passed as the
parameter.}
From the C99 standard, section 6.7.5.1, "Pointer declarators":
<quote>
EXAMPLE The following pair of declarations demonstrates
the difference between a "variable pointer to a constant
value" and a "constant pointer to a variable value".
const int *ptr_to_constant;
int *const constant_ptr;
The contents of any object pointed to by ptr_to_constant
shall not be modified through that pointer, but
ptr_to_constant itself may be changed to point to another
object. Similarly, the contents of the int pointed to by
constant_ptr may be modified, but constant_ptr itself shall
always point to the same location.
<end quote>
In the first case, the protection is not removed by pointer arithmetic
or array indexing, so ptr_to_constant+8 is also a pointer to a constant
int. The constraints can be removed by "casting away" the "constant"
quality.
If you can alter the constant via casting it is not truly a constant;
this is
regardless of what any standard says.* That is a HORRID idea, I for
one,
would NOT like it if my assertion "Denominator /= 0" suddenly became
"Denominator /= 3."
*In some languages a "constant" [rather literal] was a label/pointer
to the
location which held the value thereof and one could therefore "change
the
value of '4'" with some dirty-tricks/black-magic.
This is just "unchecked programming", also possible in Ada using address
clauses or address-to-access conversions. You get what you ask for. The
C cast syntax (being similar to safe kinds of type conversions) makes it
a bit hard to find such naughty tricks. The newer C++ standards have a
wider choice of cast operators that make it easier, perhaps as easy as
in Ada.
> *In some languages a "constant" [rather literal] was a label/pointer
> to the location which held the value thereof and one could therefore
> "change the value of '4'" with some dirty-tricks/black-magic.
A former colleague of mine spent a considerable time debugging a FORTRAN
IV program's wrong results before he found that a COMMON area contained,
in order, a one-dimensional array and a variable that was supposed to
hold the value of pi, as set up at program start. An assignment to the
array with an index that was too large had changed "pi" to something
else, with unfortunate consequences for later trigonometric calculations.
Unless your Ada program uses hardware (MMU) protection for its
memory-resident constants you can change the "constants" through
accesses that you get from address-to-access conversions.
> On Feb 6, 9:01 pm, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
>
>> Immutable scalar arguments still ire mutable within C and C++ subprograms.
>
> Scalar arguments are passed by copy, so whether they are immutable or
> mutable within the function is independent on their origin (and the
> original is not affected by this in any way).
> Unless, of course, you have meant something else.
Exactly this. Passing mode (by copy vs. by reference) has nothing to do
with the mutability contract. Unchanged origin is no argument, because the
contract is broken [*].
Compare it to FORTRAN-IV. You can pass INTEGER*4 where REAL*4 is expected.
The original view (INTEGER*4) is not affected by the view within the
subprogram (REAL*4). Is it OK? No, lessons learned, we know this is not OK.
---------------------
* const T is not substitutable for T [almost everywhere].
Agreed. I recently found another bug in a C program that was caused
by changing the value of a "passed-by-copy" parameter. Of course the
caller was unaffected but the algorithm inside the function was
broken.
In Ada, if you write:
procedure Foo (Arg : in Integer) is
begin
Arg := 3;
end Foo;
you get a compile-time error.
void foo (int arg) { arg = 3; }
then it "works". In the case I'm talking about, the assignment broke
an invariant inside the function, such that the function had bad side-
effects.
--
Ludovic Brenta.
> > Scalar arguments are passed by copy, so whether they are immutable or
> > mutable within the function is independent on their origin (and the
> > original is not affected by this in any way).
> > Unless, of course, you have meant something else.
>
> Exactly this. Passing mode (by copy vs. by reference) has nothing to do
> with the mutability contract. Unchanged origin is no argument, because the
> contract is broken [*].
The contract is not broken. The function cannot modify the original
value that is passed by copy (this being the very nature of "copy")
and the caller cannot even reason about what happens behind the
signature.
For example:
void foo(int i);
and then:
int i = 5;
foo(i);
The i at the caller side cannot be modified and the caller is isolated
from foo's internals. For the sake of argument, foo might be even
written in... Ada.
And for the sake of argument you can assume that this:
void foo(int i)
{
// implementation goes here
}
is equivalent to this:
procedure Foo (I : in Integer) is
Local_Copy_Of_I : Integer := I;
begin
-- implementation in terms of Local_Copy_Of_I goes here
end Foo;
So, no, the contract is not broken.
Unless, of course, you have meant something else.
--
> On Feb 7, 9:38�am, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
>
>>> Scalar arguments are passed by copy, so whether they are immutable or
>>> mutable within the function is independent on their origin (and the
>>> original is not affected by this in any way).
>>> Unless, of course, you have meant something else.
>>
>> Exactly this. Passing mode (by copy vs. by reference) has nothing to do
>> with the mutability contract. Unchanged origin is no argument, because the
>> contract is broken [*].
>
> The contract is not broken. The function cannot modify the original
> value that is passed by copy (this being the very nature of "copy")
> and the caller cannot even reason about what happens behind the
> signature.
It is broken because const T is not substitutable for T. Mutators of the
type T are not operations of const T. q.e.d.
P.S. Contract is between two parties.
P.P.S. You may have a working program full of unstated, idiotic,
meaningless, broken contracts. This is how modern S/W industry works.
I think the "change the value of 4" reference above is literally that and
does not mean changing a language accessible constant containing the value
of 4.
I made this mistake once with a old Fortran compiler when I was a student.
The constant would be placed into a memory location somewhere by the Fortran
compiler and then that memory location would be referenced at every point in
the code in which the literal value (the integer 4 in this example) was used
in a expression or a call to a subroutine.
In my case, I assigned (IIRC) to the calling argument within the subroutine
and the memory location containing the literal value (in this example, 4)
was changed. This meant that the value of 4 was changed for the rest of
the program.
I did not make this mistake a second time. :-)
I realised after posting, this was open to confusion by people who had not
encountered this before so a more detailed example follows. The word constant
above does not refer to a programmer defined constant, but a compiler defined
constant.
In the statement:
c = (4 * i) + 2
the compiler would allocate memory locations containing the values 4 and 2.
These memory locations would then be used in the generated code whenever the
values of 2 or 4 were found. In other words, all access, including literals,
was by reference.
This was a long time ago however.
> In Ada, if you write:
>
> procedure Foo (Arg : in Integer) is
> begin
> Arg := 3;
> end Foo;
>
> you get a compile-time error.
>
> void foo (int arg) { arg = 3; }
In C, if you write
int foo(const int arg) {
arg = 3;
}
you will get a compile time error.
If you write
int foo(const int* arg) {
*arg = 3;
}
you will get a compile time error.
For further ammunition for Verdun style language comparisons,
see function Ada.Numerics.Discrete_Random.Random.
I think there are better arguments.
I know. So? The point is that the "const" was absent in the function I
had to debug, and had been absent for about 15 years. Also, not a lot
libX11 or Motif functions have "const" arguments. This bad style had
a lot of influence on the programmers of 15 years ago whose code I
must now maintain.
In Ada, this bad style is simply not possible. In Ada, "in" really
means "in".
> If you write
>
> int foo(const int* arg) { *arg = 3; }
>
> you will get a compile time error.
I know but bugs are easier to detect because when reviewers see a
pointer argument, they know they have to look out for writes through
the pointer. Even if the pointer is to a "const", since it is
trivially easy to defeat the const-ness.
> For further ammunition for Verdun style language comparisons,
> see function Ada.Numerics.Discrete_Random.Random.
What does that mean?
> I think there are better arguments.
No. There was an old (because difficult to reproduce) bug. I spent
time finding and correcting it. This bug could not have happened in
Ada. This argument is the only relevant one to me.
--
Ludovic Brenta.
I have. Nevertheless, there is an enormous amount of successfully
operating C code in existence, and such code is not sprinkled with
checks at every opportunity. I have a hunch that the urge to focus
on pointer-related errors is something that Ada programmers like to
do because it's a class of bugs to which Ada is generally immune,
and so they can scan C code for pointers and claim that unchecked
ones are all potential errors. But there are plenty of contracts
which cannot be enforced just by using the type system, and I venture
that most Ada code does not include checks for violations in every
context in which the contract is presumed to hold. And is Ada any
more immune than C to little Bobby Tables? (<http://xkcd.com/327/>)
> This bad style had
> a lot of influence on the programmers of 15 years ago whose code I
> must now maintain.
Exactly. Style has, by hypothesis, a strong impact on on code
quality, since programming patterns and programming idioms are
passed on at every stage of becoming outdated. Programmers will
copy idioms with little consideration, especially when idioms
have become associated with revered authors, first edition.
They are not updated. They are also not updated because
compilers will not reject old code.
> In Ada, this bad style is simply not possible.
It sure is possible to write awful and dangerous Ada text, even
with the dangers hidden. While this style is possible, Ada culture
is less tolerant. (Or, more skeptical of the "practical" programmer.)
> In Ada, "in" really means "in".
No, "in" is intended to mean "in". There are two reasons that
this is not an unbreakable property. The first is demonstrated by
Ada.Numerics.Discrete_Random.Random.
But Random is the exception rather than the rule, Ada tradition
promotes the reverse effect of what C tradition promotes, I think:
by Ada's default, by the traditional idiom, "in" stands for "in".
(In the case of void safety, the language discussions include
comments bemoaning that we'll have to write "not null"
instead of marking the reverse case (may be null), thus underlining
the tradition.)
The second reason that "in" need not mean "in" is less brutal:
it is the possibility of write access to a parameter object
from within the function if the objects is also a global object.
(Global to the function). (Whence Ada getting "in out" parameters
for functions is considered an act of honesty, if I understand
comment in ARG mail correctly.) Again, this kind of access is
considered by style (and is also possible in C).
>> For further ammunition for Verdun style language comparisons,
>> see function Ada.Numerics.Discrete_Random.Random.
>
> What does that mean?
It means that Ada function Ada.Numerics.Discrete_Random.Random
only works because it violates its "contract". The parameter (a
generator object) is passed "in", but is modified. Its mutation
is one effect of calling function Random.
>> I think there are better arguments.
>
> No.
Yes, and you have named them. They seem to have a little less
to do with the language definitions, but rather with "encouragement"
to write your intentions properly.
(In at least one video (lecture? Ada UK?) Robert Dewar
has been emphasizing a cultural issue a few times).
> No, "in" is intended to mean "in". There are two reasons that
> this is not an unbreakable property. The first is demonstrated by
> Ada.Numerics.Discrete_Random.Random.
This is a poor example, because random number is indeed immutable and has
no intrinsic state. A pseudo-random generator has state, but the thing it
models is stateless. In this sense there is no difference between integer
123 and the random number uniformly distributed on [0,1]. In both cases the
corresponding computational object might have state or not. E.g. literal
can be stored in the volatile memory. Whether such implementation detail
need to be exposed (to have Reset, for example), depends on many factors.
See also Ada.Calendar.Clock.
> It means that Ada function Ada.Numerics.Discrete_Random.Random
> only works because it violates its "contract". The parameter (a
> generator object) is passed "in", but is modified. Its mutation
> is one effect of calling function Random.
Wrong. You might have an implementation of Random backed by the hardware in
which case Random would have no internal state whatsoever.
> Wrong. You might have an implementation of Random backed by the hardware in
> which case Random would have no internal state whatsoever.
I don't think a truly random implementation of
Ada.Numerics. ... .Random would be correct,
because you're allowed to reset the state to a
previously-saved state, and replay they exact same sequence
of pseudo-random numbers.
It might be useful to have a truly-random function, but
it would have to be a different one.
- Bob
>...And is Ada any
> more immune than C to little Bobby Tables? (<http://xkcd.com/327/>)
Well, yeah, somewhat. As I mentioned in another thread:
http://www.adacore.com/2010/03/22/gem-82/
http://www.adacore.com/2010/04/05/gem-83/
The second "gem" contains a reference to that very
same xkcd comic you mention above (which I found
highly amusing!).
Similar things could be done in C, but it's it's rather
more trouble.
- Bob
I don't think you've made your case. The gem says
As long as this interface is used, no errors can result
in improper input being interpreted as a command
but SQL injection problems occur because at some point,
a programmer fails to notice that there's an input that
needs to be subject to the check.
By the way, I think the gem is taking the wrong approach to
validation. There's no reason to reject strings with special
characters as invalid input. Building up SQL with user inputs
involves correctly quoting the inputs. When they're properly
quoted they can have embedded special characters and the SQL
will still be correct. Otherwise you subject users to annoying
restrictions such as not allowing those characters in their
passwords.
I would rephrase that as: style has, *in my experience*, a strong
impact on code quality.
>> In Ada, this bad style is simply not possible.
>
> It sure is possible to write awful and dangerous Ada text, even
> with the dangers hidden. While this style is possible, Ada culture
> is less tolerant. (Or, more skeptical of the "practical" programmer.)
In Ada, it is pretty difficult to hide such dangers. In the case I
was talking about, had the subprogram been written in Ada, the
programmer would have had to declare a copy of the "in" argument
explicitly or to make the argument "in out"; both would have made the
bug immediately obvious to any reviewer. The reason why the bug
eluded so many people and survived 15 years was that reviewers failed
to see the assignment as the cause for the bug. Perhaps because they
were tainted by Ada culture where, if you assign to an argument, this
necessarily means the argument is "in out".
You can always program dangerously in Ada but you have to be explicit
about it.
>>> For further ammunition for Verdun style language comparisons,
>>> see function Ada.Numerics.Discrete_Random.Random.
>
>> What does that mean?
>
> It means that Ada function Ada.Numerics.Discrete_Random.Random
> only works because it violates its "contract". The parameter (a
> generator object) is passed "in", but is modified. Its mutation
> is one effect of calling function Random.
The generator is not modified; its associated (pointed-to) state is.
When reviewing a function that takes a pointer (or an object
containing a pointer), I expect and look for writes through the
pointer, so again the bug I was talking about would not have survived
a code review.
>>> I think there are better arguments.
>
>> No.
>
> Yes, and you have named them. They seem to have a little less
> to do with the language definitions, but rather with "encouragement"
> to write your intentions properly.
> (In at least one video (lecture? Ada UK?) Robert Dewar
> has been emphasizing a cultural issue a few times).
Well, I came to wonder what intentions are conveyed by "void foo (int
arg)" and why modifying arg inside foo could be intentional. Since I
came up with no convincing reason (the only reasons being variants of
premature optimization), I concluded that the possibility of "void foo
(int arg)" as opposed to "void foo (const int arg)" was a flaw in the
C language, that cost me a lot of effort.
--
Ludovic Brenta.
I've always wondered what this "SQL sanitation" was all about. Aren't
we supposed to pass all user input as bound parameters anyway? That
even removes the need for quoting.
--
Ludovic Brenta.
You are right.
That's something from which to start an investigation.
> You can always program dangerously in Ada but you have to be explicit
> about it.
Yes.
> The generator is not modified; its associated (pointed-to) state is.
The state is "its" state; the LRM does not seem to mention
a pointer. GNAT uses a limited record with no pointers in
it, but does refer to components through non-standard
'Unrestricted_Access inside the function.
GNAT: "
-- The design of this spec is very awkward, as a result of Ada 95 not
-- permitting in-out parameters for function formals (most naturally
-- Generator values would be passed this way). "
I think it is fair to say that (a) Random modifies the generator's
state and that (b) the specification does not reflect that Random
acts as this state changing subprogram.
> Well, I came to wonder what intentions are conveyed by "void foo (int
> arg)" and why modifying arg inside foo could be intentional. Since I
> came up with no convincing reason (the only reasons being variants of
> premature optimization), I concluded that the possibility of "void foo
> (int arg)" as opposed to "void foo (const int arg)" was a flaw in the
> C language, that cost me a lot of effort.
Ease of implementation when pushing parameters?
void foo(int countdown)
{
while (--countdown) {
fputc('.', stdout);
}
fputc('\n', stdout);
}
>> It means that Ada function Ada.Numerics.Discrete_Random.Random
>> only works because it violates its "contract". The parameter (a
>> generator object) is passed "in", but is modified. Its mutation
>> is one effect of calling function Random.
>
> Wrong. You might have an implementation of Random backed by the hardware in
> which case Random would have no internal state whatsoever.
An implementation does not specify the language. By the language,
Random relies on the fact that whenever it is finished, it will
deliver the "next" value ("from its generator"). The state of a
Generator can be saved and restored, so the notion of its state
is meaningful in the abstract.
Since we are discussion the effects of subprogram specs on programmer
intuition, Random's parameter Gen being of mode "in" is at odds with
the usual meaning of "in".
I suppose you could save the sequence of values returned so far, and the state
would be an index into that sequence.
--
Jeff Carter
"We burst our pimples at you."
Monty Python & the Holy Grail
16
> Since we are discussion the effects of subprogram specs on programmer
> intuition, Random's parameter Gen being of mode "in" is at odds with
> the usual meaning of "in".
Likewise Ada.Text_IO --
procedure Set_Page_Length (File : File_Type; To : Count);
as a particularly "wrong" example. But see AI95-00057:
http://www.ada-auth.org/cgi-bin/cvsweb.cgi/ais/ai-00057.txt?rev=1.6
(practicality wins over purity).
> On 07.02.11 16:21, Dmitry A. Kazakov wrote:
>
>>> It means that Ada function Ada.Numerics.Discrete_Random.Random
>>> only works because it violates its "contract". The parameter (a
>>> generator object) is passed "in", but is modified. Its mutation
>>> is one effect of calling function Random.
>>
>> Wrong. You might have an implementation of Random backed by the hardware in
>> which case Random would have no internal state whatsoever.
>
> An implementation does not specify the language. By the language,
> Random relies on the fact that whenever it is finished, it will
> deliver the "next" value ("from its generator"). The state of a
> Generator can be saved and restored, so the notion of its state
> is meaningful in the abstract.
That is specific to the procedure Reset, which is not essential for random
number generation.
> Since we are discussion the effects of subprogram specs on programmer
> intuition, Random's parameter Gen being of mode "in" is at odds with
> the usual meaning of "in".
Not at all. It is Reset, which probably is. Random is perfectly OK.
> I suppose you could save the sequence of values returned so far, and the
> state would be an index into that sequence.
Good point.
I guess the state would be the whole sequence, plus
an index into it, or something like that.
- Bob
You mean a numeric literal (to use the Ada terms).
> In the statement:
>
> c = (4 * i) + 2
>
> the compiler would allocate memory locations containing the values 4 and
> 2.
> These memory locations would then be used in the generated code whenever
> the
> values of 2 or 4 were found. In other words, all access, including
> literals,
> was by reference.
>
> This was a long time ago however.
Must have been. I remember hearing this story in college. And it supposely
was old then. I've always thought of it as a sort of urban legend. But I
suspect that it has more to do with the instruction set of the target
machine. On the Intel architectures that I've worked on my entire working
life, it wouldn't make sense to have a global memory location holding a
literal (the literals can be folded into instructions at very little cost).
But I can imagine cases where that wouldn't be true.
Randy.
I don't think this would meet A.5.2(28), "[...] All generators are
implicitly initialized to an unspecified state that does not vary from
one program execution to another[...]".
I suppose that (reverting to the current Numerics.Discrete_Random) means
"for the same compiler on the same architecture"? You could hardly
expect GCC 4.3.3 on PowerPC to produce the same output as GCC 4.6.0 on
x86_64. Or could it even mean "for executions of a specific build of a
specific program on a specific machine"?
> > The contract is not broken. The function cannot modify the original
> > value that is passed by copy (this being the very nature of "copy")
> > and the caller cannot even reason about what happens behind the
> > signature.
>
> It is broken because const T is not substitutable for T. Mutators of the
> type T are not operations of const T. q.e.d.
q.e.d. what?
I have an impression that we are (again) in the mode where you bring
arbitrarily twisted definitions in order to prove your point.
What contract is broken?
Why do you want to substitute one type by another? These are not
generics, nor other type-dependent constructs, so type substitutions
are out of the picture.
Or (because I have no idea what is your point, so I'm guessing), you
can easily use const T to provide the actual value for the parameter
of T:
void foo(int i);
and then:
const int j = 7;
foo(j);
Type substitution does not happen here. The types interact by means of
initialization, but this interaction has nothing to do with
substitution.
So, no contract is broken. (q.e.d.?)
Unless, of course, you have meant something else.
--
> I remember hearing this story in college. And it supposely was old then.
> I've always thought of it as a sort of urban legend.
I had to debug a FORTRAN 66 program (my own, sadly, written in 1967) that
was going astray in a statement of the form:
IF (X) 101, 102, 103
The problem was that a previous call to a library subroutine, along the
lines of:
CALL SOLVE(A, X, B, 0.0)
was updating the accuracy parameter 0.0 to the residual norm of the solution
(or some such accuracy indication), and this was making the implicit
comparison with 0.0 in the later IF statement go wrong.
--
Bill Findlay
with blueyonder.co.uk;
use surname & forename;
I think C was originally designed for the PDP-11. It's been ages
since I did anything with that processor, but my recollection is that
you could include a 16-bit numeric literal in an instruction. It
would take up an extra word, but putting the literal somewhere else
and including the address in the instruction would also take up an
extra word, so you wouldn't save anything. However, if the literal
needed to be 32 bits then I can imagine it might have made sense to
stick it in a global.
-- Adam
That's what I meant by "variants of premature optimization". If all
parameters were const, as in Ada, the programmer would simply declare
a local variable, like in Ada. And the bug I was talking about would
become blatantly obvious.
BTW, like I said, *every* time I look at C code, I see a bug. In your
case, foo has undefined behavior if countdown is negative.
Also, note how the Ada version of this function does not use a
variable at all; it uses a for loop inside of which K is constant. And
it does not have your bug:
procedure Foo (Countdown : in Natural) is
begin
for K in 1 .. Countdown loop
Put ('.');
end loop;
Next_Line;
end Foo;
So, "ease of implementation" is not a good reason. The thing you
implemented "easily" had a bug.
--
Ludovic Brenta.
>
> BTW, like I said, *every* time I look at C code,
This is funny, but I understand what you mean.
> I see a bug. In your
> case, foo has undefined behavior if countdown is negative.
>
If I remember my C, since "while" will keep running until its argument
becomes false, which in C means "zero", then this means if countdown is
negative the above code will keep decrementing countdown until it hits
-infinity? :) and the program will crash or do something else?
> Also, note how the Ada version of this function does not use a
> variable at all; it uses a for loop inside of which K is constant. And
> it does not have your bug:
>
> procedure Foo (Countdown : in Natural) is
> begin
> for K in 1 .. Countdown loop
> Put ('.');
> end loop;
> Next_Line;
> end Foo;
>
> So, "ease of implementation" is not a good reason. The thing you
> implemented "easily" had a bug.
>
> --
> Ludovic Brenta.
Ada has so many nice things in it. I think its type system is one
of the best features of Ada.
--Nasser
Actually, the bug also triggers if countdown == 0 upon entry into foo,
because of the *prefix* decrement operator.
The behavior is undefined by the language but most C programmers
assume that counter will wrap around. In fact, when GCC 4.5
introduced aggressive optimizations based on the knowledge that
overflow semantics was undefined in C, a *lot* of existing programs
were suddenly broken. In fact, only their incorrect assumptions
("hidden contracts") were broken. The damage was so severe that the
GCC folks had to demote this aggressive optimization to a non-default
compiler switch :(
--
Ludovic Brenta.
>> void foo(int countdown)
>> {
>> while (--countdown) {
>> fputc('.', stdout);
>> }
>> fputc('\n', stdout);
>> }
>
> That's what I meant by "variants of premature optimization".
See why Ada cannot be efficient? ;-)
>> Ease of implementation when pushing parameters?
>>
>> void foo(int countdown)
>> {
>> while (--countdown) {
>> fputc('.', stdout);
>> }
>> fputc('\n', stdout);
>> }
>
> That's what I meant by "variants of premature optimization". If all
> parameters were const, as in Ada, the programmer would simply declare
> a local variable, like in Ada. And the bug I was talking about would
> become blatantly obvious.
As I said, maybe that was easier to implement for the
C language and compiler makers.
You'd have to demonstrate that an Ada procedure, needing
an extra loop variable, will produce the same code.
(Hopefully, it does not!)
> BTW, like I said, *every* time I look at C code, I see a bug. In your
> case, foo has undefined behavior if countdown is negative.
We are playing Monopoly, aren't we. You won't be winning a
single C programmer with this style of non-framed single
issue logic triggered only by stupid misuse of a perfectly
working solution.
As Hyman said, there are tons of well working C programs out there,
very likely using some int i, plus increment or decrement.
Well, there is a related CVE every other week, but (a) there
aren't equally many Ada programs, (b) if programmers don't know
what INT_MAX + 1 is, they shouldn't be programming in C, and
(c) by single case logic, Ada's type system has not prevented
deaths in Ariane 5 anyway.
Yes. This is the root cause why C is so bad for everyone else.
> You'd have to demonstrate that an Ada procedure, needing
> an extra loop variable, will produce the same code.
> (Hopefully, it does not!)
Hopefully, it does, except for the precondition check that Countdown
>= 0.
>> BTW, like I said, *every* time I look at C code, I see a bug. In your
>> case, foo has undefined behavior if countdown is negative.
>
> We are playing Monopoly, aren't we. You won't be winning a
> single C programmer with this style of non-framed single
> issue logic triggered only by stupid misuse of a perfectly
> working solution.
I don't know what you mean with Monopoly but, if I were trying to win
over C programmers, I'd be on comp.lang.c right now. And this is
*not* "stupid misuse of a perfectly working solution", it is strict
interpretation of the explicit contract that you yourself had
provided: the function "foo" accepts a *signed* integer as its
parameter. The "stupid misuse" is yours; you should have used
"unsigned int" to express your contract, and then accounted for the
possibility that the parameter could be zero.
> As Hyman said, there are tons of well working C programs out there,
> very likely using some int i, plus increment or decrement.
And the majority of these tons of "working" programs were broken the
instant GCC started to optimize overflows away.
> Well, there is a related CVE every other week, but (a) there
> aren't equally many Ada programs, (b) if programmers don't know
> what INT_MAX + 1 is, they shouldn't be programming in C, and
Ah that's a good one. It is perfectly true. It is well known that C
was created only for perfect programmers who never make mistakes. But
these programmers, if they exist, do not need C; they can simply "cat
> a.out", can't they?
> (c) by single case logic, Ada's type system has not prevented
> deaths in Ariane 5 anyway.
Huh?
--
Ludovic Brenta.
>> (c) by single case logic, Ada's type system has not prevented
>> deaths in Ariane 5 anyway.
>
> Huh?
You don't even know this?
The problem was the conscious decision not to re-test the Ariane 4
software against Ariane 5 due to the incorrect assumption that the
accelerations involved had not changed. It had everything to do with
management and nothing with software engineering (in fact the software
engineers were denied the right to test the software against "secret"
Ariane 5 predicted trajectory data).
There were no deaths involved.
So: huh?
--
Ludovic Brenta.
No. Stupid programmers can easily misuse foo() just as
Ada programmers can easily write violations of index
constraints. Hopefully, they write exception handlers.
But wait. If the handlers are just as smart as their
use of indexing variables... Isn't their ambition flawed
by a logical contradiction? If the handlers are just
as good as the checked indexing...
> BTW, like I said, *every* time I look at C code, I see a bug. In your
> case, foo has undefined behavior if countdown is negative.
You took the bait. Fine.
Strict contracts in subprogram profiles. Ha!
void foo2(int jump)
/* make sure jump % 2 == 0! */
{
...
}
Write that in Ada 2005 to demonstrate the glorious power of
your subtype constraints. When was it that compilers will
support aspects reliably?
No. Stupid programmers use "int" when they really mean "positive", and
they don't even explain that to users of their APIs. Not even in
comments. Then they say bugs are every one else's fault but theirs.
--
Ludovic Brenta.
Hello;
From wiki:
http://en.wikipedia.org/wiki/Ariane_5_Flight_501
"Because of the different flight path, a data conversion from a 64-bit
floating point to 16-bit signed integer value caused a hardware
exception (more specifically, an arithmetic overflow, as the floating
point number had a value too large to be represented by a 16-bit
signed integer). Efficiency considerations had led to the disabling of
the software handler (in Ada code) for this error trap"
So, the problem was "disabling of the software handler".
So, it seems to me that Ada worked as expected? It DID detect the
overflow.
But someone has disabled run-time exception handler.
--Nasser
Easy:
procedure Foo2 (Jump : in Natural);
-- Jumps by 2 * Jump
Thereby eliminating the very *possibility* that 2*jump can be odd!
--
Ludovic Brenta.
See? You talk about C functions as if there was a general
case: C functions, plural. Recurring bugs in them, causing
undefined behavior. I made up the same thing for rocket flights
in general. The generalizing argument is structurally equivalent!
Both arguments are pointless because people won't listen.
(Watch Sarah P. to see why arguing about foo(int undefined) is
pointless.)
There is a chance of human error (in C, in rockets).
Smart engineers don't make them (in C, in rockets).
C or Ada have had little to do with these human
errors cause by the wrong assumptions
(about foo() and about Ariane 4 modules).
Here it is, again, my foo() vs Ariane 4/5:
Construction made by a human, in C,
knowing how it was going to be used.
Construction made by a human, in Ada,
knowing how it was going to be used.
In general, where is the difference?
Exactly!
And they are right because it *is* everyone else fault
if they call a function they don't understand!
Because their programs do demonstrably work, and they
do so *because* they call their own functions as they
should be called.
Obviously.
>> Strict contracts in subprogram profiles. Ha!
>>
>> void foo2(int jump)
>> /* make sure jump % 2 == 0! */
>> {
>> ...
>>
>> }
>>
>> Write that in Ada 2005 to demonstrate the glorious power of
>> your subtype constraints. When was it that compilers will
>> support aspects reliably?
>
> Easy:
>
> procedure Foo2 (Jump : in Natural);
> -- Jumps by 2 * Jump
>
> Thereby eliminating the very *possibility* that 2*jump can be odd!
See how you couldn't provide a solution, but made up
a different case? Now
Foo2 (Jump => Natural'Last);
No, I provided a proper contract that reflects the problem at hand
rather than your fragile solution.
Now
>
> Foo2 (Jump => Natural'Last);
procedure Foo2 (Jump : in Natural) is
begin
if Jump <= Natural'Last / 2 then
Baz (2 * Jump);
else
declare
Half_Jump : constant Natural := Jump / 2;
Remainder : constant Natural := Jump mod 2;
begin
Baz (2 * Half_Hump);
Baz (2 * Half_Jump);
if Remainder /= 0 then
Baz (2 * Remainder);
end if;
end;
end if;
end Foo2;
And that's assuming Long_Long_Integer is not wide enough for the
calculations.
--
Ludovic Brenta.
> procedure Foo2 (Jump : in Natural) is
> begin
> if Jump <= Natural'Last / 2 then
> Baz (2 * Jump);
> else
> declare
> Half_Jump : constant Natural := Jump / 2;
> Remainder : constant Natural := Jump mod 2;
> begin
> Baz (2 * Half_Hump);
> Baz (2 * Half_Jump);
> if Remainder /= 0 then
> Baz (2 * Remainder);
> end if;
> end;
> end if;
> end Foo2;
>
> And that's assuming Long_Long_Integer is not wide enough for the
> calculations.
There is lots of assuming here. The original "contract" read
void foo2(int jump)
/* make sure jump % 2 == 0! */
{
...
}
(And int includes negative values.) The idea could have
been that the least significant bit is not set. Or the idea
could have been different. The point is, neither language
provides the means of expressing the idea using types.
Not yet.
The knee jerk reaction of the Ada programmer is to invent
a different problem and its solution and show off.
It's counterproductive.
So, more specifically, define an integer subtype T whose values
do not include the values of the static constants A, B, and C,
assuming that each of A, B, and C will be members of a
proper subrange of T'Range. Make sure that membership in T
can be checked at compile time.
Do the same for run-time values A, B, and C.
What I'd like to point out is that we need a better utility
function. A function that properly measures program quality.
I imagine that type checking quality vs the type system's
flexibility of the respective languages form one variable
of the utility function.
Yes. I was in too much of a rush when I wrote the original posting.
>> In the statement:
>>
>> c = (4 * i) + 2
>>
>> the compiler would allocate memory locations containing the values 4 and
>> 2.
>> These memory locations would then be used in the generated code whenever
>> the
>> values of 2 or 4 were found. In other words, all access, including
>> literals,
>> was by reference.
>>
>> This was a long time ago however.
>
> Must have been. I remember hearing this story in college. And it supposely
> was old then. I've always thought of it as a sort of urban legend. But I
> suspect that it has more to do with the instruction set of the target
> machine. On the Intel architectures that I've worked on my entire working
> life, it wouldn't make sense to have a global memory location holding a
> literal (the literals can be folded into instructions at very little cost).
> But I can imagine cases where that wouldn't be true.
>
As someone else has commented in another posting, something like this also
happened to them, but I can see how it might be considered a urban legend if
you have only used more mainstream hardware.
In my case, this was during the 1980's while I was still in the sixth form
(the US equivalent of the sixth form would be high school) and was taking
advanced (for the time :-)) programming classes at the local higher education
establishment as part of my sixth form education.
The miniframe which was installed there was a obscure miniframe from
Sperry/Unisys and not something mainstream like a PDP-11. The operating
system and compilers were quite limited even by the standards of what was
available in those days and given these other limitations, this global
sharing and allowed overwriting of literals by the generated compiler
code is not a surprise in hindsight.
Simon.
--
Simon Clubley, clubley@remove_me.eisner.decus.org-Earth.UFP
Microsoft: Bringing you 1980s technology to a 21st century world
The key is that FORTRAN IV required all parameter passing to be by
reference. In a call like:
CALL SP (4)
how do you pass "4" by reference? You have to create a variable. The
compiler was clever enough to create only one variable for each static
value. But inside SP, nothing prevented you from modifying the formal...
Fortunately, our machine had memory protection, and the compiler
generated those pseudo-variables in read-only memory. So you had a
memory access violation, which was easier to debug than when all 4's
were changed to 5's
--
---------------------------------------------------------
J-P. Rosen (ro...@adalog.fr)
Adalog a déménagé / Adalog has moved:
2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX
Tel: +33 1 45 29 21 52, Fax: +33 1 45 29 25 00
--
Si les chats miaulent et font autant de vocalises bizarres, c’est pas pour
les chiens.
“I am fluent in ASCII” [Warren 2010]
>> Below is an
>> example of code I would like to be able to implement.
>>
>> ...
>> unsigned int print(char *message, unsigned int line)
>> {
>> char *vidmem = (char *) 0xb8000;
>> unsigned int i= 0;
>>
>
> I do not have any experience about this part of Ada, but maybe I can
> give you a "pointer" to some bootstrapping information. Maybe you could
> want to use an attribute representation clause for Vidmem'Address (see
> Section 13.3 of the RM) . With some improvisation, I would write
> something like
>
> type Vidmem_Array is array (natural range <>) of Character;
> Vidmem : Vidmem_Array (0 .. Max_Size);
>
> for Vidmem'Address use 16#000B_8000#;
There may be a real issue here. This was segment register = B800 and
offset register = 0000, for the base address. Ada does not make any
distinction about segment and offset. There should be something stated in
the compiler's documentation, to know how it maps linear address to
segment:offset address. Otherwise, I feel the result is unpredictable
because ambiguous: the compiler may have at least two options. The first
one would be to work with a fixed segment as the base address for each
memory pool and then the address attribute would match the offset only
(Pascal compilers was working this way inside of units). The second one
would be to use the standard convention at that time, to have offset range
in 0..15 and the remaining of the address held by the segment… the was the
Borland C++ way when using the so called Huge Memory model. These two
interpretation ends into different things. In short, there is no way to
believe “for Vidmem'Address use 16#000B_8000#” can be OK without further
investigations.
In C or C++, a function parameter is an ordinary variable
which is initialized by the an expression in the function
call. If the parameter is not const, it may then be changed
during the operation of the function. C has worked this way
for something like forty years, so being surprised by this
now seems disingenuous.
There's no particular reason that a function parameter should
not be permitted to be changed, notwithstanding that you found
a case where doing so caused an error. One puts implementation,
for example, is
int puts(char *s)
{
while (*s) if (putchar(*s++) == EOF) return EOF;
return putchar('\n');
}
and a C programmer would be puzzled when told that this function
would be better if he had to use another variable initialized to
the value of 's' before getting down to business.
I rather think that it's Ada that got it wrong here, by choosing
to combine the concept of passing by value or reference and the
concept of immutability. Evidence for that is the fact that in
several places the Ada standard has to say that a parameter must
be passed by reference while the declaration of the subprogram
does not give you that information (because the parameter is 'in').
But if C has been working this way for forty years, and its
programmers still can't get it right (as evidenced by the fact that
Ludovic found a bug), then it's arguable that the language is making
it too easy to write incorrect code and too difficult to write code
you can count on. A two-line example such as yours doesn't count as
evidence against that; it's easy to get really short subroutines
correct. The problems come up when you have larger routines. And
I've run into this problem with a program I maintain, in a language
(not C) that also allows value parameters to be modified locally.
Someone has gone into it and written a statement that uses the
parameter, assuming that it's the value that the caller gave it,
without realizing that someone else had written a statement up above
that changed the parameter's value.
There's always going to be a religious war between those who want a
language that doesn't make them do extra annoying stuff and putting
annoying restrictions on their code, and those who prefer a language
that provides some degree of protection against stupid errors (not
only their own, but stupid errors made by the other programmer down
the hall who got laid off two years ago). I'm certainly not going to
resolve the religious war here. But since I do so much of my work
with other people's code, I do have a preference for the latter, and I
think Ada got this one right.
-- Adam
Quoting Wikipedia: "As it was an unmanned flight, there were no
victims". What "deaths" are you referring to? I think that's what
Ludovic meant by "Huh?".
-- Adam
Sure, I agree 100% with you. The question what was the cost to have the
code running fine? We can also see many assembly code running fine, what
what the cost per line?
I'm not a guy having fun playing with a language that want to kill me or
empty my pocket because I need to spend energy on intrinsic problems. I
want to concentrate to my application domain not to C/C++ idiosyncrasy!
So for what it's worth I agree 100% with Ludovic. I too see bugs in
every C line of code...
Pascal.
--
--|------------------------------------------------------
--| Pascal Obry Team-Ada Member
--| 45, rue Gabriel Peri - 78114 Magny Les Hameaux FRANCE
--|------------------------------------------------------
--| http://www.obry.net - http://v2p.fr.eu.org
--| "The best way to travel is by means of imagination"
--|
--| gpg --keyserver keys.gnupg.net --recv-key F949BD3B
Sure, I agree 100% with you. The question what was the cost to have the
Sure, I agree 100% with you. The question what was the cost to have the
An anecdotal reference, and not even showing the problematic code,
does not constitute compelling evidence. I don't see why the same
error might not arise with respect to any variable that a programmer
assumes has one value while in fact it has been changed to another.
> A two-line example such as yours doesn't count as
> evidence against that; it's easy to get really short subroutines
> correct. The problems come up when you have larger routines.
Then don't write large routines. In fact, you should switch to my
new imaginary programming language Lydia. In that language, all
subprograms can be no longer than five lines, since made-up statistics
show that 95% of all programming errors occur in subprograms which are
longer.
You mean like Ada, where you have to know the difference
between bounded and unbounded strings before you can just
go ahead and focus on your domain?
> So for what it's worth I agree 100% with Ludovic.
> I too see bugs in every C line of code...
Perhaps that makes you unsuited to look at C code?
C has been in continuous use for around forty years,
and there are enormous amounts of working C code
everywhere. If you believe that every line of C code
is broken, I submit that the fault lies in you, not
in the code - you may not understand how to evaluate
C code properly.
The story I heard about the FORTRAN-IV compiler on the CDC 6400 I learned on in
1975 was that certain "common" values (0, 1, 2, ...) were stored in global
memory locations, used by all programs. So if one program changed the value of
"2", every other program would get the wrong value thereafter until the system
was restarted.
I never tested it, and never heard of it happening, so I can't say if it was true.
--
Jeff Carter
"Mr. President, we must not allow a mine-shaft gap!"
Dr. Strangelove
33
> On Feb 7, 10:24�am, "Dmitry A. Kazakov" <mail...@dmitry-kazakov.de>
> wrote:
>
>>> The contract is not broken. The function cannot modify the original
>>> value that is passed by copy (this being the very nature of "copy")
>>> and the caller cannot even reason about what happens behind the
>>> signature.
>>
>> It is broken because const T is not substitutable for T. Mutators of the
>> type T are not operations of const T. q.e.d.
>
> q.e.d. what?
1. const T /= T
2. not (const T <: T)
> I have an impression that we are (again) in the mode where you bring
> arbitrarily twisted definitions in order to prove your point.
>
> What contract is broken?
>
> Why do you want to substitute one type by another?
Because one is passed as a parameter where another is declared, expected
and used. This is substitution.
> These are not
> generics, nor other type-dependent constructs, so type substitutions
> are out of the picture.
Not at all. const T and T are distinct [sub]types. The types are different
because the sets of operations defined on them are.
--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
The C programmers vs the Ada programmers kind of deaths
that inevitably happen in those rocket functions that,
by the laws of the wrong language, have shown that failure
must ensue because we have seen it once. -- You know, they
did use Ada in that rocket and it exploded! said some authority
and therefore, by Palinian logic, anything other than Ada is
better and rockets should not explode above the heads of American
people. That's not what we pay our taxes for, for funding
a government compiler that makes rockets explode. -- But it
was a different rocket. -- Well, I think that if it happened
in another rocket, we should be happy that not both of
them have made people die. -- They didn't use GNAT either. --
So you are saying that GNAT is not an Ada compiler? They were
so proud to use Ada and then the rocket exploded. -- But
no one died in Ariane 5. It was unstaffed. -- I think you
are dishonest to the American people. They've got a right to
learn that using "in" mode parameters in preference over
passing copies can kill our astronauts. -- Ariane 5 is a
European rocket. -- It lifts off in French Guiana.
That's very close to Florida, isn't it? --
This kind of death. Of argument. The birth of rhetoric.
Things are made up for the sake of argument. Which is why
I mixed several perfectly comparable rockets: they all
look the same, as we all can see. Too much of this
happens in language comparisons.
It is very good to learn about traceable cases of the danger
inherent in some language, such as using mutable scalars
from the largest possible scope they can have inside
functions (parameters in C; better in Ada?).
I wish they were collected in some unbiased public wiki,
together with a kind of cost analysis, metaphorical or real,
of the observed effects. Wouldn't this be a nice addition
to the Style Guide? A chapter on Bug Avoidance Techniques (BAT)?.
I bet Les Hatton would find this chapter to be in line with his
evaluation of style guides.
> C has been in continuous use for around forty years,
> and there are enormous amounts of working C code
> everywhere.
Slavery was around for thousands of years. Slaves built the Colosseum and
Pyramids. How silly of us would be not to deploy such an established
economical and technological framework.
> If you believe that every line of C code
> is broken, I submit that the fault lies in you, not
> in the code - you may not understand how to evaluate
> C code properly.
Yes, there is something wrong with us...
Remember the episode in Space Quest III with slave programmers working at
ScumSoft company? I bet they used C. (:-))
From the Ada mind-set point of view, this is correct. But in the C mind set,
there are no APIs. Header files are just complications you have to create to
keep the compiler happy; nobody reads them, and they're generally not commented
or formatted nicely. You are expected (and all C coders expect) that before
using a function, you have read and understand its code and know what it will do.
I once worked on an Ada project that had been done by C people. The package
specs were badly formatted collections of declarations with no comments; the
developers viewed them as header files that no one would read. There were
minimal comments in the bodies, and everyone was expected to read the body code
to understand how to use the operations.
The problem with this is it violates Dijkstra's adage that we have small brains
and must do our work with them. Few of us can keep all the code for the entire
project in his head, and there are times when what one remembers is incorrect.
And so, eventually, in any real project, even the best of developers make errors
under such circumstances.
So, when talking to a C coder, "You said int, so negative values should be OK"
isn't considered a valid argument. The response will be that you are supposed to
have read the code and realized what would happen if you called it with a
negative value. If you then go ahead and do so, you get what you asked for.
> Then don't write large routines.
Is there evidence that a mechanism necessary for combining
the smaller routines leads to fewer errors? I'd like to
believe this if its statistically true.
(And put aside worries about impractical overhead that, for
example, objects have in some languages.)
I was just joking, as the rest of the post should have made clear.
Slavery is analogous to Ada, whose rigidity forces
everyone into strict, regimented, and gray coding
practices instead of permitting the open delightful
freedom given by C.
Can we stop this stupidity now?
Ah, now I understand. No true Scotsman would write bad Ada!