indentify if argument is char or float

R

Richard Heathfield

(e-mail address removed) said:
I follow you. However, strtod expects a parameter of type char **. I
can pass the address of a pointer, or I can just pass a variable of
type char **.

Yes, that will /compile/, but it won't /work/.
Either way, the only thing that changes is how I
dereference the pointer when the function returns.

No. What changes is that, if you do this wrong, it won't work. Think about
*why* strtod needs a char **, and maybe enlightenment will occur, although
frankly I'm beginning to doubt that it will.
 
B

bwaichu

Richard said:
Yes, that will /compile/, but it won't /work/.

Here's a debugging session that shows that it doesn't matter what
approach you take:

with invalid input:

$ gdb strtof
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and
you ar
welcome to change it and/or distribute copies of it under certain
condition
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
details.
This GDB was configured as "amd64-unknown-openbsd4.0"...
(gdb) break main
Breakpoint 1 at 0x40089f: file strtof.c, line 13.
(gdb) run 1a1a1a1a1a
Starting program: /anime/strtof 1a1a1a1a1a

Breakpoint 1, main (argc=2, argv=0x7f7ffffc98a8) at strtof.c:13
13 input = argv[1];
(gdb) info locals
endptr = (char **) 0x7f7ffffc9890
input = 0x7f7ffffc9d50 "/anime/strtof"
output = 4.1461860541929491e-317
(gdb) step
15 if(argc < 2) {
(gdb) info locals
endptr = (char **) 0x7f7ffffc9890
input = 0x7f7ffffc9d5e "1a1a1a1a1a"
output = 4.1461860541929491e-317
(gdb) step
21 output = strtod(input, endptr);
(gdb) until
22 if (**endptr != '\0') {
(gdb) info locals
endptr = (char **) 0x7f7ffffc9890
input = 0x7f7ffffc9d5e "1a1a1a1a1a"
output = 1
(gdb) p **endptr
$1 = 97 'a'
(gdb) step
23 perror("string to double conversion failed");
(gdb)

with valid input:

$ gdb strtof
GNU gdb 6.3
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and
you a
e
welcome to change it and/or distribute copies of it under certain
conditio
s.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for
details
This GDB was configured as "amd64-unknown-openbsd4.0"...
(gdb) break main
Breakpoint 1 at 0x40089f: file strtof.c, line 13.
(gdb) run 12.2
Starting program: /anime/strtof 12.2

Breakpoint 1, main (argc=2, argv=0x7f7ffffc8ca8) at strtof.c:13
13 input = argv[1];
(gdb) info locals
endptr = (char **) 0x7f7ffffc8c90
input = 0x7f7ffffc9150 "/anime/strtof"
output = 4.1461860541929491e-317
(gdb) step
15 if(argc < 2) {
(gdb) info locals
endptr = (char **) 0x7f7ffffc8c90
input = 0x7f7ffffc915e "12.2"
output = 4.1461860541929491e-317
(gdb) step
21 output = strtod(input, endptr);
(gdb) until
22 if (**endptr != '\0') {
(gdb) info locals
endptr = (char **) 0x7f7ffffc8c90
input = 0x7f7ffffc915e "12.2"
output = 12.199999999999999
(gdb) p **endptr
$1 = 0 '\0'
(gdb) step
26 (void)printf("%f\n", (float)output);
(gdb) until
12.200000
27 exit(0);
(gdb)

I have no problem being wrong. Please show me that the above does not
/work/.
I do agree that checking for output == 0 was wrong.
 
K

Keith Thompson

Here's a debugging session that shows that it doesn't matter what
approach you take:
[snip]

It shows no such thing. At best, it shows that it can *appear* to
work.

The second argument to strtod() is char **endptr. You pass a
pointer-to-pointer-to-char so that the function can *store* a
pointer-to-char value in the location you specify.

If you call it like this:

char **endptr; /* uninitialized */
... strtod("123", endptr) ...

you are passing the value of an uninitialized variable to the
function. An uninitialized pointer points to some arbitrary location
in memory; you're asking strtody() to write to that arbitrary
location. If it happens, by random chance, that that arbitrary
location is one that you're able to write to, the code will appear to
work -- but it's likely to clobber some other variable.

Don't do that.

By contrast, if you do this:

char *end;
... strtod("123", &end) ...

then "end" is uninitialized, but you're passing its *value*, not its
*address*, so that's ok. You're still passing a value of type char**
to strtod(), but now the value specifies the address of the char*
variable that you've declared. You're now asking strtod() to write a
value to "end" -- which is exactly what you want.
 
F

Flash Gordon

Here's a debugging session that shows that it doesn't matter what
approach you take:

I have no problem being wrong. Please show me that the above does not
/work/.
I do agree that checking for output == 0 was wrong.

The standard states that it invokes undefined behaviour. Therefore it
invokes undefined behaviour. One possible result of undefined behaviour
is it doing what you expect. Another possible result is your program
crashing. A third result is your computer being fried. A forth possible
result is your computer growing arms and legs, tying you up, putting you
in the boot of the car, and driving the car off a cliff killing you.

Richard Heathfield is a highly respected member of this group and
acknowledged around here as knowing a few things about C. You are just
learning. Think about who is more likely to be wrong and reread the
messages explaining why you are wrong.
 
R

Richard Heathfield

Keith Thompson said:

By contrast, if you do this:

char *end;
... strtod("123", &end) ...

then "end" is uninitialized, but you're passing its *value*, not its
*address*, so that's ok.

Er, actually you're passing the value of its address. The expression is okay
because &end doesn't evaluate end. It yields the address of end. That
address's value is what strtod gets.
 
R

Richard Heathfield

(e-mail address removed) said:
Here's a debugging session that shows that it doesn't matter what
approach you take:

No, it's a debugging session that shows that you don't understand that
debugging sessions do not define the C language.

I have no problem being wrong.
Evidently.

Please show me that the above does not /work/.

"If the subject sequence is empty or does not have the expected
form, no conversion is performed; the value of nptr is stored in the
object pointed to by endptr, provided that endptr is not a null
pointer."

If endptr does not point to an object (and is not NULL) then the behaviour
is necessarily undefined.
I do agree that checking for output == 0 was wrong.

This isn't a matter of debate. The C language definition says the way you're
doing it is wrong, and therefore the way you're doing it is wrong.
 
K

Keith Thompson

Richard Heathfield said:
Keith Thompson said:


Er, actually you're passing the value of its address. The expression is okay
because &end doesn't evaluate end. It yields the address of end. That
address's value is what strtod gets.

Sorry, my fingers have betrayed me again.

What I meant, of course, was:

then "end" is uninitialized, but you're passing its *address*, not
its *value*, so that's ok.

As always, thanks for keeping me on my toes.
 
K

Keith Thompson

Richard Heathfield said:
(e-mail address removed) said:

No, it's a debugging session that shows that you don't understand that
debugging sessions do not define the C language.

Upthread, Richard advised you to *think* about why strtod() takes a
char** arguments. That was excellent advice; following it would have
saved you some time. Rather than thinking about it you experimented
and got some meaningless results.
 
C

Chris Torek

Richard Heathfield wrote:
I follow you. However, strtod expects a parameter of type char **. I
can pass the address of a pointer, or I can just pass a variable of
type char **.

Not exactly: you can pass the *value* of a variable of type
"char **". (You never pass "a variable" in C, only "a value".)
Here's the same program with endptr declared as char **:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>

int
main(int argc, char **argv) {

char **endptr;
char *input;
double output;

input = argv[1];

if(argc < 2) {
(void)printf("usage: strtof <digit>\n");
exit(1);
}


output = strtod(input, endptr);
if (**endptr != '\0') { /* dereference of pointer changes */
perror("string to double conversion failed");
exit(1);
}
(void)printf("%f\n", (float)output);
exit(0);
}

The above passes gcc -Wall -pedantic without warning.

You should add "-O" (and -ansi, but in this case it is the -O that
is important):

% gcc -O -Wall -ansi -pedantic -o t t.c
t.c: In function `main':
t.c:9: warning: `endptr' might be used uninitialized in this function

The reason you need -O to get this warning is that gcc only runs
the dataflow analysis phase when optimizing, and it is the dataflow
analysis phase that discovers use of uninitialized variables.

If I move "char **endptr" down to the third variable, and compile
it on BSD/OS with shlicc (which uses gcc), without -O, and run the
resulting binary, I get:

% shlicc -o t t.c
% ./t 123.456
Segmentation fault (core dumped)

Turning on optimization makes it run, as does using "cc" (which
uses the dynamic linker instead of static shared libraries). Both
of these are "mere luck" (*bad* luck :) ) -- with -O, gcc moves
the variables around, and when using the dynamic linker, the place
"endptr" lives on the stack happens to get some value stored in it
that points to some other valid address within the process. The
static shared library ("shlicc") version is much more efficient,
though, so that the stack is still "mostly clean" and endptr happens
to be NULL (as long as it is the third variable, not the first).
 
B

bwaichu

Richard said:
Er, actually you're passing the value of its address. The expression is okay
because &end doesn't evaluate end. It yields the address of end. That
address's value is what strtod gets.

I am going to spin this discussion off into another thread. I never
distinguished a difference between the two methods above of passing a
parameter.
 
B

bwaichu

Chris said:
The reason you need -O to get this warning is that gcc only runs
the dataflow analysis phase when optimizing, and it is the dataflow
analysis phase that discovers use of uninitialized variables.

This helps a lot since I tend to use debugging sessions to find errors.
And I am wrong.

Thanks!
 
O

Old Wolf

(gdb) info locals
endptr = (char **) 0x7f7ffffc9890
input = 0x7f7ffffc9d5e "1a1a1a1a1a"
output = 4.1461860541929491e-317
(gdb) step
21 output = strtod(input, endptr);
(gdb) until

strtod writes to the pointer you pass it. This debug trace shows
that your system allowed you to write to a random memory
address (ie. 7f7fffc9890) without any bad effects, in this case.

I'm sure many people will tell you that writing to a random
memory address can, in general, cause a segfault, or even
worse, the ability for violation of security privileges (and they
would be right).

Tip: If you ever apply for a C programming job, don't tell
the interviewer that you like to write code that writes to
random memory addresses.
 
B

bwaichu

Old said:
strtod writes to the pointer you pass it. This debug trace shows
that your system allowed you to write to a random memory
address (ie. 7f7fffc9890) without any bad effects, in this case.

I'm sure many people will tell you that writing to a random
memory address can, in general, cause a segfault, or even
worse, the ability for violation of security privileges (and they
would be right).

Tip: If you ever apply for a C programming job, don't tell
the interviewer that you like to write code that writes to
random memory addresses.

I think I follow you. It's because I never initialized char *endptr
since I declared it as char **endptr. So when I passed it as a
parameter,
the compiler randomly assigned the location of char *endptr.

And you don't have to worry about me applying for any C programming
job. I have worked in finance for the last 6 years.

I posted to this thread because I am writing a httpd server (off topic
and OS specific), and I need to practice parsing out the request
strings (not quite C strings). I figured this would give me a good
refresher on data validation. (I have never used strtod until
yesterday.) The good thing is that I learned something new by posting
code that wasn't quite right. I will keep posting code if it means I
will receive good responses.

Thanks!
 
A

av

Bill said:
But I don't understand why you will consider
it a failure if the user enters "0"--that seems like a valid
input. It might be more appropriate to check
for *endptr == argv[1], or perhaps **endptr == '\0'.

I changed endptr to a char *. I don't think it really matters as I
would be de-referencing that variable anyway. I would like to hear why
you prefer that approach. Is it code readability?

Here's a working version:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>

int
main(int argc, char **argv) {

char *endptr;

why don't change above in:
char **endptr=0;
for me it has to segfault

the right definition should be
char *endptr=0;
 

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

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top