Ex 7-5

M

mdh

Hi All,
I have been struggling with this for a few hours. The exercise is a
variation on the RPN calculator, asking to use scanf or sscanf for
input.

I have included the ( partially completed) code below, but this is the
puzzle. ( It's from my old standby Tondo and Gimpel)

Firstly, it compiles with out any errors!!!

If I set a breakpoint in main, and step through the code, it returns
the expected output.
If I set a breakpoint in 'getop' at the first 'while' statement, it
seems to skip the line "sscanf(lastc, "%c", &c);"
If no breakpoint is set, I get the same output as the line above, but
cannot be sure, where the error is.


ps...if the formatting below looks wrong, please tell me / please let
me know how to present it for the clc.

/********/

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>


#define MAXOP 10
#define NUMBER '0'

double pop(void);
void push (double);
int getop(char *);

int main (int argc, const char * argv[])
{
char line[MAXOP];
int type;
while ( (type = getop(line)) != EOF)
switch (type)
{
case NUMBER:
push (atof(line));
break;
case '+':
push (pop() + pop());
break;
case '\n':
printf("\t%.5g", pop());
break;
default:
printf("Error: unknown command \'%s\'\n", line);
break;
}

return 0;
}


int getop(char line[])
{
int c, rc, i;
static char lastc[] = " ";

sscanf(lastc, "%c", &c); /** I ***think** this line is the problem?
***/
lastc[0] = ' ';

while ( (line[0]=c) == ' ' || c == '\t')
if ( scanf("%c", &c) == EOF)
c=EOF;
line[1]= '\0';
if ( !isdigit(c) && c != '.')
return c;
i = 0;
if ( isdigit(c))

do
{
rc = scanf("%c", &c);
if (!isdigit(line[++i] = c))
break;
}while (rc != EOF);

if ( c == '.')

do
{
rc = scanf("%c", &c);
if (!isdigit(line[++i]= c))
break;
}while (rc != EOF);


line= '\0';


if ( rc != EOF)
lastc[0] = c;

return NUMBER;

}

#define MAXSTACK 20

double stack[MAXSTACK];
double *dptr = stack;
double *eptr = stack + MAXSTACK;


double pop(void){
if ( dptr >= stack)
return *(--dptr) ;
else{
printf("Error: Stack Empty");
return 0.00;
}

}



void push (double d){

if ( dptr < eptr)
*dptr++ = d;
else
printf("Error: Stack Full");

}
 
K

Keith Thompson

mdh said:
I have been struggling with this for a few hours. The exercise is a
variation on the RPN calculator, asking to use scanf or sscanf for
input.

I have included the ( partially completed) code below, but this is the
puzzle. ( It's from my old standby Tondo and Gimpel)

Firstly, it compiles with out any errors!!!
[...]

ps...if the formatting below looks wrong, please tell me / please let
me know how to present it for the clc.

First: Don't use tabs when posting to Usenet.

I can't even tell what tabstop setting you're using. Some of your
indentation is consistent with 4-column tabs, but not all of it.

I find that "indent -nut -kr -nce" gives reasonable results. "-nut"
say not to use tabs, "-kr" says to use K&R format, and "-nce" says not
to "cuddle" else; the latter generates this:

if (cond) {
stmt;
}
else {
stmt;
}

rather than this:

if (cond) {
stmt;
} else {
stmt;
}

I also strongly prefer to use braces for compound statements, even
when there's only a single controlled statement.

These are matters of taste, and reasonable people will violently
disagree with me on any or all of these points (though anyone who
disagrees about tabs is just wrong).

[...]
int c, rc, i;
static char lastc[] = " ";

sscanf(lastc, "%c", &c); /** I ***think** this line is the problem?
***/
[...]

It's certainly *a* problem. sscanf's "%c" option requires a char*;
you're giving it an int*. Hilarity ensues.
 
B

Ben Bacarisse

mdh said:
ps...if the formatting below looks wrong, please tell me / please let
me know how to present it for the clc.

Already commented on so I'll move on...
/********/

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <ctype.h>


#define MAXOP 10
#define NUMBER '0'

double pop(void);
void push (double);
int getop(char *);

int main (int argc, const char * argv[])
{
char line[MAXOP];
int type;
while ( (type = getop(line)) != EOF)
switch (type)
{
case NUMBER:
push (atof(line));
break;
case '+':
push (pop() + pop());
break;
case '\n':
printf("\t%.5g", pop());
break;
default:
printf("Error: unknown command \'%s\'\n", line);
break;
}

return 0;
}


int getop(char line[])

I'd pass in the maximum size of line rather than reply on the #define.
{
int c, rc, i;
static char lastc[] = " ";

sscanf(lastc, "%c", &c); /** I ***think** this line is the problem?
***/
lastc[0] = ' ';

while ( (line[0]=c) == ' ' || c == '\t')
if ( scanf("%c", &c) == EOF)
c=EOF;

OK, the problem of using %c with an int * has been covered, but I would
suggest you getchar() (or fgetc) instead anyway. It makes everything
much simpler. Of course you have to avoid storing the "last" string,
but see below for more on that.

The whole thing can be:

while ((c = getchar()) == ' ' || c == '\t') /* do nothing */;
line[0] = c;

Although note that setting line[0] to (char)EOF is odd...
line[1]= '\0';
if ( !isdigit(c) && c != '.')
return c;
i = 0;
if ( isdigit(c))

do
{
rc = scanf("%c", &c);
if (!isdigit(line[++i] = c))

scanf is wrong again here. If it fails you will add another digit
(the old c) to line. I don't know of that matters, but it looks
wrong. Also you must check that there is room for the digit.
break;
}while (rc != EOF);

if ( c == '.')

do
{
rc = scanf("%c", &c);
if (!isdigit(line[++i]= c))
break;
}while (rc != EOF);


line= '\0';


I'd try to avoid these two loops. I am sure you can come up with a
way to have only one!
if ( rc != EOF)
lastc[0] = c;

I think is is better to put the char back on the stream rather doing
this fiddling with "last". For one thing, some other part of the
program may one day need to get input and having that char that ended
the last input stored here prevents that from ever being done.
return NUMBER;

}

#define MAXSTACK 20

double stack[MAXSTACK];
double *dptr = stack;
double *eptr = stack + MAXSTACK;


double pop(void){
if ( dptr >= stack)
return *(--dptr) ;

Oops. If dptr == stack you can't access *--dptr.
 
R

Ron Ford

int c, rc, i;
static char lastc[] = " ";

sscanf(lastc, "%c", &c); /** I ***think** this line is the problem?
***/
[...]

It's certainly *a* problem. sscanf's "%c" option requires a char*;
you're giving it an int*. Hilarity ensues.

He did get it right as far as copying from Tondo & Gimpel is concerned.
The format specifier would need to be %d. Beyond that, it might be a
suitable solution for the clc wiki for K&R solns, where none is posted for
7-5.
 
M

mdh

[...]>    int c, rc, i;
   static char lastc[] = " ";
   sscanf(lastc, "%c", &c);  /**  I ***think** this line is the problem?
***/

[...]

It's certainly *a* problem.  sscanf's "%c" option requires a char*;
you're giving it an int*.  Hilarity ensues.

--


As Ron Ford noted (below), T&G ( which I missed) declare c as an int
( presumably to hold EOF) but use it as a "char" in sscanf. Was this
correct at some point in C? In other words did something change after
their book was published (1989 according to my version). If this is
indeed true( that it was correct at some point) could someone
elaborate on what changed and how this now causes "hilarity" or, as
has been my forte lately, ub? :) thanks.
 
M

mdh

Already commented on so I'll move on...

I need to figure out how to make those tabs go away when I post here.
Will work on that.


Oops.  If dptr == stack you can't access *--dptr.

OOps..I missed that too...but I could not get past the other problem
first.


I think their solution was also mainly to show the use of scanf/sscanf
as opposed to writing the best code..so your thoughts on a better
solution are appreciated.

Thank you Ben.
 
I

Ian Collins

mdh said:
[...]> int c, rc, i;
static char lastc[] = " ";
sscanf(lastc, "%c", &c); /** I ***think** this line is the problem?
***/
[...]

It's certainly *a* problem. sscanf's "%c" option requires a char*;
you're giving it an int*. Hilarity ensues.


As Ron Ford noted (below), T&G ( which I missed) declare c as an int
( presumably to hold EOF) but use it as a "char" in sscanf. Was this
correct at some point in C? In other words did something change after
their book was published (1989 according to my version).

I doubt it.
If this is
indeed true( that it was correct at some point) could someone
elaborate on what changed and how this now causes "hilarity" or, as
has been my forte lately, ub? :) thanks.

Assuming sizeof int > sizeof char, the value read will but placed in the
first byte of c. The first byte may be the most significant byte, so
you will end up with a very big number.
 
B

Barry Schwarz

[...]>    int c, rc, i;
   static char lastc[] = " ";
   sscanf(lastc, "%c", &c);  /**  I ***think** this line is the problem?
***/

[...]

It's certainly *a* problem.  sscanf's "%c" option requires a char*;
you're giving it an int*.  Hilarity ensues.

--


As Ron Ford noted (below), T&G ( which I missed) declare c as an int
( presumably to hold EOF) but use it as a "char" in sscanf. Was this
correct at some point in C? In other words did something change after
their book was published (1989 according to my version). If this is
indeed true( that it was correct at some point) could someone
elaborate on what changed and how this now causes "hilarity" or, as
has been my forte lately, ub? :) thanks.

You seem to be mixing the return value from sscanf with the object in
which it stores the converted input. sscanf will return EOF if the
end of file indicator is set for the stream. It will never store EOF
as the result of a %c conversion specification.

This is different than fgetc whose return value is an int so that both
characters and EOF can be returned as appropriate.

Passing an int* where %c expects a char* invokes undefined behavior.
Consider the case where the two pointer types have different sizes or
representations or are passed by different mechanisms. Even if
everything is the same, if sizeof(int) >1 then sscanf will only update
part of the int. Which part depends on what endian your system is.
 
M

mdh

You seem to be mixing the return value from sscanf with the object in
which it stores the converted input.  sscanf will return EOF if the
end of file indicator is set for the stream.  It will never store EOF
as the result of a %c conversion specification.


Yes...I missed that ...thank you.


Passing an int* where %c expects a char* invokes undefined behavior.

Hmmm...first time I have seen T&G wrong, that is why I thought that
perhaps when they wrote their solution ( in 1989), it was permissible
to do something like that...not, let me hasten to add, that I picked
that up before it was pointed out.
Thanks for the correction of return value versus conversion
specifications and storage....I had not clearly distinguished between
the two...another reason for exercises.
 
R

Ron Ford

Hmmm...first time I have seen T&G wrong, that is why I thought that
perhaps when they wrote their solution ( in 1989), it was permissible
to do something like that...not, let me hasten to add, that I picked
that up before it was pointed out.
Thanks for the correction of return value versus conversion
specifications and storage....I had not clearly distinguished between
the two...another reason for exercises.

I've seen the T&G solns get outdated, but this one is wrong from the
git-go. I thought before that you would change the format specifier on the
scanf; now I think you need to change the type declaration of c to char.

Please repost when you get something that compiles and behaves.
 
K

Keith Thompson

Ian Collins said:
mdh wrote: [...]
If this is
indeed true( that it was correct at some point) could someone
elaborate on what changed and how this now causes "hilarity" or, as
has been my forte lately, ub? :) thanks.

Assuming sizeof int > sizeof char, the value read will but placed in the
first byte of c. The first byte may be the most significant byte, so
you will end up with a very big number.

And if it happens to be the least significant byte, and the int
happens to have been (un)initialized to 0, it could appear to work.
 
M

mdh

I've seen the T&G solns get outdated, but this one is wrong from the
git-go.  
Please repost when you get something that compiles and behaves.


Ron...as a newbie, it does give me a kick to look at their solution
and see that it is incorrect. Will work on a correct solution.
 
M

mdh

Ian Collins said:
mdh wrote: [...]
If this is
indeed true( that it was correct at some point) could someone
elaborate on what changed and how this now causes "hilarity" or, as
has been my forte lately, ub?  :) thanks.
And if it happens to be the least significant byte, and the int
happens to have been (un)initialized to 0, it could appear to work.

But it still invokes ub?...certainly the enemy of the clc! :)
 
B

Ben Bacarisse

mdh said:
On Sep 12, 10:13 pm, Barry Schwarz <[email protected]> wrote:

Hmmm...first time I have seen T&G wrong, that is why I thought that
perhaps when they wrote their solution ( in 1989), it was permissible
to do something like that

I don't think so. There was experience of C on word addressed
machines long before 1989. On such systems it was common for the
conversion from, say, int * to char * to perform arithmetic. Treating
one as the other without a conversion (as must happen if you pass a
type of pointer that is not the expected one) accesses the wrong
address -- often dramatically the wrong address depend on the scheme
used.

It is also true that there was a lot of code around at that time that
assumed all pointers were the same and that C's pointer conversion
rules were there just to please the compiler, but I am sure that C's
designers knew about this issue and designed C with it in mind.

C developed, in part from B and BCPL. Both B and BCPL had a
word-based view of objects and could be implemented on character, bit
or word addressed machines with equal ease. One of C's goals was to
have better treatment for character data. It is not an accident that
pointer conversions in the original C require a cast -- Ritchie must
have know that on some architectures the conversion would require an
actual arithmetic operation.

I don't have T&G, but from some of the examples I have seen posted I
can't help thinking that it may not be the stellar example that I
had always assumed it to be. That aside, passing an int * to scanf
with a %c format is wrong, even in the oldest C.

I don't think the exercise is meant to be read as "use *only* scanf".
I think it means replace the messy loops that read the number with a
scanf call. Replacing getch with scanf("%c", ...) is daft, but if one
was forced to, the only sane way is with a function:

int scanc(void)
{
char c;
return scanf("%c", &c) == 1 ? c : EOF;
}

so you can get behaviour that is as convenient as getch.
 
J

James Kuyper

mdh said:
Ian Collins said:
mdh wrote: [...]
If this is
indeed true( that it was correct at some point) could someone
elaborate on what changed and how this now causes "hilarity" or, as
has been my forte lately, ub? :) thanks.
And if it happens to be the least significant byte, and the int
happens to have been (un)initialized to 0, it could appear to work.

But it still invokes ub?...certainly the enemy of the clc! :)

The fact that the behavior is undefined is what permits the compiler to
generate code which does the things described above. "undefined
behavior" is the answer to "why?"; the explanations you've just been
give the answer to "how?".
 
M

mdh

[...]>    int c, rc, i;
   static char lastc[] = " ";
   sscanf(lastc, "%c", &c);  /**  I ***think** this line is the problem?
***/

[...]

It's certainly *a* problem.  sscanf's "%c" option requires a char*;
you're giving it an int*.  Hilarity ensues.

May I just pursue this a little further....sadly, once again, having
given this some more thought!!! :)

K&R often use c ( declared as an int) to assign a character. So, for
example, p69, getline takes 2 arguments. A char array, and an integer.


int c;
while ( .......(c=getchar() != EOF).....)

I understand why c is declared as an int here, but why is then
different from scanf where one uses an int to hold a character?

Thanks
 
V

vippstar

int scanc(void)
{
char c;
return scanf("%c", &c) == 1 ? c : EOF;
}

so you can get behaviour that is as convenient as getch.

That is not equal to getchar. (however it *might* be equal to that
'getch' you are talking about...)
Assume char is signed.

fix:

int scanc(void) {
char c;
return scanf("%c", &c) == 1 ? (unsigned char)c : EOF;
}

I hope you understand why this is necessary. Else this would not work:

int c;
c = scanc();
if(isupper(c)) /* ... */
 
B

Ben Bacarisse

That is not equal to getchar. (however it *might* be equal to that
'getch' you are talking about...)

It was in the context of the K&R exercise that uses a function (that
they write) called getch. I don't think my function is the same but
then that was not really the point. If you *have* to use scanf to
read a char, then wrap it in a function that gives you an error
return.
Assume char is signed.

fix:

int scanc(void) {
char c;
return scanf("%c", &c) == 1 ? (unsigned char)c : EOF;
}

I hope you understand why this is necessary.

I understand why it is better. I hope you understand that I was
writing about old code in K&R that happily uses char to store the
result of getchar() -- in their code for getch for example[1]. In K&R
C (old but on topic I think) "the definition of C guarantees that any
character in the machine's standard character set will never be
negative" (page 40). Of course, exactly what this standard character
set is left a little vague, but the effect is that in K&R C you can
use char to store characters. You still need int to be able to see
EOF, but if you are reading text files, char is OK for storage and
there is not need for casts on the is* macros or when returning stored
characters. It is understandable, but sad, that this simple fact is
not true any more.

I am sure that if I'd not been reading my old copy of K&R (to see what
was being discussed) I'd have written your more portable version :)
Else this would not work:

int c;
c = scanc();
if(isupper(c)) /* ... */

That's fine in K&R C (with my version of scanc)! I don't want anyone
to get the idea I think you are wrong. You are right. It would have
been better had I written the more portable modern version. I am only
explaining why I went all sloppy.

[1] I hope that, if I had K&R2, I would have been seeing code that is
rather more portable. Does the code for getch() in chapter 4 still
(in the second edition, I mean) store the result of getchar() in a
char and return these (uncast) as ints? I don't have it (or access to
decent library) so I can't check.
 
B

Ben Bacarisse

[1] I hope that, if I had K&R2, I would have been seeing code that is
rather more portable. Does the code for getch() in chapter 4 still
(in the second edition, I mean) store the result of getchar() in a
char and return these (uncast) as ints? I don't have it (or access to
decent library) so I can't check.

In my copy, K&R2, 3rd printing, it still does.

Thanks. That is, of course, less than maximally portable but it fits
with K&R's rather more relaxed style.
 
K

Keith Thompson

mdh said:
[...]>    int c, rc, i;
   static char lastc[] = " ";
   sscanf(lastc, "%c", &c);  /**  I ***think** this line is the problem?
***/

[...]

It's certainly *a* problem.  sscanf's "%c" option requires a char*;
you're giving it an int*.  Hilarity ensues.

May I just pursue this a little further....sadly, once again, having
given this some more thought!!! :)

K&R often use c ( declared as an int) to assign a character. So, for
example, p69, getline takes 2 arguments. A char array, and an integer.


int c;
while ( .......(c=getchar() != EOF).....)

RH already pointed out the misplaced parentheses.
I understand why c is declared as an int here, but why is then
different from scanf where one uses an int to hold a character?

getchar() returns an int value, so of course you want to assign the
result to an int object. (You can *mostly* get away with assigning
the result to a char object, since the assignment implicitly converts
the value to the required type before storing it. I say "mostly"
because it causes problems if getchar() returns either EOF or a value
greater than CHAR_MAX; the latter is possible if plain char is
signed.)

scanf with "%c" requires a *pointer* to a char object; scanf will then
store a 1-byte value in that object. If you pass it a pointer to an
int object, you're lying to it. There's no implicit conversion to
save you; char and int values can be mixed, but char* and int* values
cannot.
 

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,995
Messages
2,570,225
Members
46,815
Latest member
treekmostly22

Latest Threads

Top