Strange behaviour

G

gamehack

Hi all,

I've been testing out a small function and surprisingly it does not
work okay. Here's the full code listing:
#include "stdlib.h"
#include "stdio.h"

char* escaped_byte_cstr_ref(char byte);

int main (int argc, const char * argv[])
{
char* t = escaped_byte_cstr_ref('!');
printf(t);
free(t);

return 0;
}

char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);

char* str = malloc(4);

str[0] = '%';
str[1] = buff[0];
str[2] = buff[1];
str[3] = 0x0;

return str;
}

The program does not print anything - neither in bash nor in gdb.
Although I do remember getting it running on XP with migw(currently I'm
testing on OS X).

Thanks for your help,
gamehack
 
I

Ian Collins

gamehack said:
Hi all,

I've been testing out a small function and surprisingly it does not
work okay. Here's the full code listing:
#include "stdlib.h"
#include "stdio.h"

char* escaped_byte_cstr_ref(char byte);

int main (int argc, const char * argv[])
{
char* t = escaped_byte_cstr_ref('!');
printf(t);

printf ("%s\n", t);

Otherwise you don't flush stdout.
 
A

Andrew Poelstra

gamehack said:
Hi all,

I've been testing out a small function and surprisingly it does not
work okay. Here's the full code listing:
#include "stdlib.h"
#include "stdio.h"

To ensure that you are using standard headers and not ones in your
working directory, use
#include <stdio.h>
#include said:
char* escaped_byte_cstr_ref(char byte);

int main (int argc, const char * argv[])
{
char* t = escaped_byte_cstr_ref('!');
printf(t);
free(t);

return 0;
}
All good so far.
char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);
To simply copy a string, use strcpy(char *dest, char *source);
I'm not sure that you used the correct printf argument (%X).
char* str = malloc(4);

str[0] = '%';
str[1] = buff[0];
str[2] = buff[1];
str[3] = 0x0;
This is okay, but the intermediate variable buff is unneccessary, unless
you need to validate the input.
return str;
}

The program does not print anything - neither in bash nor in gdb.
Although I do remember getting it running on XP with migw(currently I'm
testing on OS X).

Try inserting my modifications, and see if that fixes it.
 
A

Andrew Poelstra

Ian said:
gamehack said:
Hi all,

I've been testing out a small function and surprisingly it does not
work okay. Here's the full code listing:
#include "stdlib.h"
#include "stdio.h"

char* escaped_byte_cstr_ref(char byte);

int main (int argc, const char * argv[])
{
char* t = escaped_byte_cstr_ref('!');
printf(t);

printf ("%s\n", t);

Otherwise you don't flush stdout.

Disregard my last post; it appears that

printf (t);

should be

printf ("%s", t);
 
I

Ian Collins

Andrew said:
gamehack said:
char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);
To simply copy a string, use strcpy(char *dest, char *source);
I'm not sure that you used the correct printf argument (%X).
The OP wants the value in hex, not a copy. It would have been clearer
if the function had a sensible name!
char* str = malloc(4);

str[0] = '%';
str[1] = buff[0];
str[2] = buff[1];
str[3] = 0x0;
This is okay, but the intermediate variable buff is unneccessary, unless
you need to validate the input.
See above.
 
C

cane

gamehack ha scritto:
The program does not print anything - neither in bash nor in gdb.
Although I do remember getting it running on XP with migw(currently I'm
testing on OS X).

this works:

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

char* escaped_byte_cstr_ref(char byte);

int main (int argc, const char * argv[])
{
char* t = escaped_byte_cstr_ref('!');
printf("%s",t);
free(t);

return 0;
}

char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
char* str = malloc(4);

sprintf(buff, "%X", byte);

str[0] = '%';
str[1] = buff[0];
str[2] = buff[1];
str[3] = 0x0;

return str;
}
 
I

Ian Collins

Ian said:
gamehack said:
Hi all,

I've been testing out a small function and surprisingly it does not
work okay. Here's the full code listing:
#include "stdlib.h"
#include "stdio.h"

char* escaped_byte_cstr_ref(char byte);

int main (int argc, const char * argv[])
{
char* t = escaped_byte_cstr_ref('!');
printf(t);


printf ("%s\n", t);

Otherwise you don't flush stdout.
Right fix, wrong explanation, sorry.

The first argument to printf is a format string.
 
O

Old Wolf

gamehack said:
char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);

Undefined behaviour -- %X expects an unsigned int, but you
pass it a char. Also, you cause a buffer overflow if byte has
any value other than 0 - 99 (ie. values greater than 99, and
values less than 0). In the case of negative bytes, the
buffer overflow will be particularly bad, as -1 will output
FFFFFFFF if you are on IA32 architecture.

Either use snprintf, or this:

char buff[50];
sprintf(buff, "%X", (unsigned int)byte);

where '50' is larger than any int will be in the foreseeable future.
 
A

Andrew Poelstra

Old said:
gamehack said:
char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);

Undefined behaviour -- %X expects an unsigned int, but you
pass it a char. Also, you cause a buffer overflow if byte has
any value other than 0 - 99 (ie. values greater than 99, and
values less than 0).
In the case of negative bytes, the
buffer overflow will be particularly bad, as -1 will output
FFFFFFFF if you are on IA32 architecture.

Either use snprintf, or this:

char buff[50];
sprintf(buff, "%X", (unsigned int)byte);

where '50' is larger than any int will be in the foreseeable future.

Let's assume that we have an unsigned char (which is a false assumption
in most cases).

A 3-element array can take a 2-digit string, which in this case will be
a hex number. All the values that fit into uchar (0x00-0xFF) fit this
criteria.

It is true, however, that negative numbers will expand very uncleanly.
 
A

Andrew Poelstra

Andrew said:
Old said:
gamehack said:
char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);

Undefined behaviour -- %X expects an unsigned int, but you
pass it a char. Also, you cause a buffer overflow if byte has
any value other than 0 - 99 (ie. values greater than 99, and
values less than 0).
In the case of negative bytes, the
buffer overflow will be particularly bad, as -1 will output
FFFFFFFF if you are on IA32 architecture.

Either use snprintf, or this:

char buff[50];
sprintf(buff, "%X", (unsigned int)byte);

where '50' is larger than any int will be in the foreseeable future.

Let's assume that we have an unsigned char (which is a false assumption
in most cases).

A 3-element array can take a 2-digit string, which in this case will be
a hex number. All the values that fit into uchar (0x00-0xFF) fit this
criteria.

It is true, however, that negative numbers will expand very uncleanly.

Note: no standard-ASCII character will exceed 0x7F, and therefore the
negative number problem is practically nonexistant. Of course, in theory
it is possible that this function will be passed bad data, and so
precautions should be taken (in this case, simply using an unsigned char
will eliminate all issues, because anything bigger than 255 will be
wrapped).
 
D

Dik T. Winter

> > char* escaped_byte_cstr_ref(char byte)
> > {
> > char buff[3];
> > sprintf(buff, "%X", byte);
>
> Undefined behaviour -- %X expects an unsigned int, but you
> pass it a char.

Not so. An int is passed. You can not pass chars to a variadic function.
However, changing "char byte" above to "unsigned char byte" above would be
an improvement.
> pass it a char. Also, you cause a buffer overflow if byte has
> any value other than 0 - 99 (ie. values greater than 99, and
> values less than 0).

I have no idea where you base that "99" on. There will be problems if
it is larger than 255 or smaller than 0.
> Either use snprintf, or this:
>
> char buff[50];
> sprintf(buff, "%X", (unsigned int)byte);
>
> where '50' is larger than any int will be in the foreseeable future.

This is the wrong solution. Consider what happens if byte == -1.
 
O

Old Wolf

Dik said:
Old Wolf said:
gamehack said:
char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);

Undefined behaviour -- %X expects an unsigned int, but you
pass it a char.

Not so. An int is passed.

C99 7.19.6.1#9 says:

If any argument is not the correct type for the corresponding
conversion specification, the behaviour is undefined.

3.3 defines "argument" as:

expression in the comma-separated list bounded by the
parentheses in a function call expression ...

6.5.2.2#6 defines the default argument promotions:

the integer promotions are performed on each argument,
and arguments that have type float are promoted to double.

The latter two quotes support the hypothesis that the "argument"
is the expression before the default promotions occur (as
opposed to the result of the default argument promotions).

In the above code, the argument is the expression:

byte

and it undoubtedly has type char, thus falling foul of 7.19.6.1#9 .

I have no idea where you base that "99" on. There will be
problems if it is larger than 255 or smaller than 0.

Sorry, I was thinking of printing in decimal, for some reason.
Either use snprintf, or this:

char buff[50];
sprintf(buff, "%X", (unsigned int)byte);

where '50' is larger than any int will be in the foreseeable future.

This is the wrong solution. Consider what happens if byte == -1.

Then there will be no buffer overflow, unless the unsigned int
has more than 4*49 = 196 bits, which I don't foresee happening.

(I'm not speculating on what the intent of the code is -- just
making sure it doesn't cause a buffer overflow. The OP clearly
did not consider the negative-char case anyway, so he is
welcome to start considering it now).
 
A

Andrew Poelstra

Old said:
Dik said:
Old Wolf said:
gamehack wrote:
char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);
Undefined behaviour -- %X expects an unsigned int, but you
pass it a char.
Not so. An int is passed.

C99 7.19.6.1#9 says:

If any argument is not the correct type for the corresponding
conversion specification, the behaviour is undefined.

3.3 defines "argument" as:

expression in the comma-separated list bounded by the
parentheses in a function call expression ...

6.5.2.2#6 defines the default argument promotions:

the integer promotions are performed on each argument,
and arguments that have type float are promoted to double.

The latter two quotes support the hypothesis that the "argument"
is the expression before the default promotions occur (as
opposed to the result of the default argument promotions).

In the above code, the argument is the expression:

byte

and it undoubtedly has type char, thus falling foul of 7.19.6.1#9 .
No, a char is a type of int. Therefore, it will be promoted to an
unsigned long int, or whatever it expects.
 
I

Ian Collins

Old said:
Dik said:
Old Wolf said:
gamehack wrote:

char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);

Undefined behaviour -- %X expects an unsigned int, but you
pass it a char.

Not so. An int is passed.


C99 7.19.6.1#9 says:

If any argument is not the correct type for the corresponding
conversion specification, the behaviour is undefined.
But char is of the correct type, it will be promoted to int.

extern void fn( int );

char m;
short n;
int o;
fn( m );
fn( n );
fn( o );

Are all correct.
 
K

Keith Thompson

Andrew Poelstra said:
Old said:
Dik said:
gamehack wrote:
char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);
Undefined behaviour -- %X expects an unsigned int, but you
pass it a char.
Not so. An int is passed.
C99 7.19.6.1#9 says:
If any argument is not the correct type for the corresponding
conversion specification, the behaviour is undefined.
3.3 defines "argument" as:
expression in the comma-separated list bounded by the
parentheses in a function call expression ...
6.5.2.2#6 defines the default argument promotions:
the integer promotions are performed on each argument,
and arguments that have type float are promoted to double.
The latter two quotes support the hypothesis that the "argument"
is the expression before the default promotions occur (as
opposed to the result of the default argument promotions).
In the above code, the argument is the expression:
byte
and it undoubtedly has type char, thus falling foul of 7.19.6.1#9 .
No, a char is a type of int. Therefore, it will be promoted to an
unsigned long int, or whatever it expects.

Wrong, and wrong.

char is an integer type; int is another integer type. A char is not
"a type of int". (Are you thinking of the fact that character
literals are of type int?)

In the following:

void foo(unsigned long x);
...
char c;
foo(c);

c is converted to type unsigned long. This kind of conversion does
not take place for variadic functions like sprintf. In that case,
only the default argument promotions are performed; char is promoted
to int, regardless of the type specified by the format string. If the
argument is of type char, and the format string specifies a type other
than int, the behavior is undefined.

(If CHAR_MAX==UINT_MAX, char is promoted to unsigned int rather than
int; this can happen only if plain char is unsigned and
sizeof(int)==1, which can happen only if CHAR_BIT>=16.)

(Passing a signed int when an unsigned int is expected, or vice versa,
is likely to work as long as the value is within the range of both
types; the standard almost guarantees this, but doesn't *quite* say
so.)
 
M

milen.dzhumerov

gamehack said:
Hi all,

I've been testing out a small function and surprisingly it does not
work okay. Here's the full code listing:
#include "stdlib.h"
#include "stdio.h"

char* escaped_byte_cstr_ref(char byte);

int main (int argc, const char * argv[])
{
char* t = escaped_byte_cstr_ref('!');
printf(t);
free(t);

return 0;
}

char* escaped_byte_cstr_ref(char byte)
{
char buff[3];
sprintf(buff, "%X", byte);

char* str = malloc(4);

str[0] = '%';
str[1] = buff[0];
str[2] = buff[1];
str[3] = 0x0;

return str;
}

The program does not print anything - neither in bash nor in gdb.
Although I do remember getting it running on XP with migw(currently I'm
testing on OS X).

Thanks for your help,
gamehack

Thanks a lot everyone. I did a really stupid mistake.

Kind regards,
gamehack
 
O

Old Wolf

Ian said:
But char is of the correct type, it will be promoted to int.

extern void fn( int );

The section I quoted is talking about arguments to variadic
functions that match the '...' bit.
 
D

Dik T. Winter

> > "Old Wolf said:
> >> gamehack wrote:
> >>> char* escaped_byte_cstr_ref(char byte)
> >>> {
> >>> char buff[3];
> >>> sprintf(buff, "%X", byte);
> >>
> >> Undefined behaviour -- %X expects an unsigned int, but you
> >> pass it a char.
> >
> > Not so. An int is passed.
>
> C99 7.19.6.1#9 says:
> If any argument is not the correct type for the corresponding
> conversion specification, the behaviour is undefined.

So according to your reasoning the following:
char c = 'a';
printf("%c\n", c);
is undefined behaviour. (%c expects an int argument, see that section #8.)
> 3.3 defines "argument" as:
> expression in the comma-separated list bounded by the
> parentheses in a function call expression ...
>
> 6.5.2.2#6 defines the default argument promotions:
> the integer promotions are performed on each argument,
> and arguments that have type float are promoted to double.
>
> The latter two quotes support the hypothesis that the "argument"
> is the expression before the default promotions occur (as
> opposed to the result of the default argument promotions).

The argument is, but the type of the argument is not.
 
O

Old Wolf

Dik said:
So according to your reasoning the following:
char c = 'a';
printf("%c\n", c);
is undefined behaviour. (%c expects an int argument, see that section #8.)

It looks that way. Section 7 says:
hh Specifies that a following d, i, o, u, x, or X conversion
specifier applies to a signed char or unsigned char
argument

h Specifies that a following d, i, o, u, x, or X conversion
specifier applies to a short int or unsigned short int
argument

By your reasoning these modifiers could never be used, as
the argument is always an int (assuming we are talking
about systems where chars and shorts promote to int).

I think that it is inconsistent to read "argument" as the pre-
promotion argument in some clauses, and to read it as the
post-promotion argument in other clauses of the same
section.

In fact, those section 7 entries have a suffix clarifying that
they are talking about the pre-promotion argument. Perhaps
a DR is in order for section 8 to clarify that your code
should not cause UB.

The argument is, but the type of the argument is not.

In your above code, the argument is 'a' of type char. After the
default argument promotions, the argument is 'a' of type int.
Right?
 
P

pete

Old said:
It looks that way.

C99
6.3.1 Arithmetic operands
6.3.1.1 Boolean, characters, and integers

2 The following may be used in an expression
wherever an int or unsigned int may be used:

— An object or expression with an integer type
whose integer conversion rank is less than
the rank of int and unsigned int.
 

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,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top