Why does this work? (rot13 function)

E

Eirik

This is a little function I wrote, inspired by the thread
"Urgent HELP! required for Caesar Cipher PLEASE"

$ cat /home/keisar/bin/c/ymse/rot13.h

char rot13(char character)
{
int changed;
changed = character - 'a' + 'n';
return changed;
}

I find two things strange about this code:
1) I don't have to specify that b should be replaced by n,
c by o and so on. How come?
2) The function returns a char(char rot13), but changed
is an integer. How is that possible?
 
J

Joona I Palaste

Eirik said:
This is a little function I wrote, inspired by the thread
"Urgent HELP! required for Caesar Cipher PLEASE"
$ cat /home/keisar/bin/c/ymse/rot13.h
char rot13(char character)
{
int changed;
changed = character - 'a' + 'n';
return changed;
}
I find two things strange about this code:
1) I don't have to specify that b should be replaced by n,
c by o and so on. How come?

Actually, if we're strict about the C standard, you DO have to
specify that b should be replaced by n and so on. You happen to be
using a character set where 'a'...'z' are contiguous, but the C
standard allows for other character sets.
Provided 'a'...'z' are contiguous, the above code works, because
chars are just integer types in C.
2) The function returns a char(char rot13), but changed
is an integer. How is that possible?

Because chars are integer types. Unlike Pascal, C does not require
conversion functions between characters and their numeric values,
but treats them as interchangable by themselves.
 
H

Hallvard B Furuseth

Eirik said:
This is a little function I wrote, inspired by the thread
"Urgent HELP! required for Caesar Cipher PLEASE"

$ cat /home/keisar/bin/c/ymse/rot13.h

char rot13(char character)
{
int changed;
changed = character - 'a' + 'n';
return changed;
}

See Joona's reply. Except, it only works for characters a-m and A-M.
It gives the wrong result for n-z and N-Z. Try this:

#include <ctype.h>

char rot13(char character)
{
int changed;
if (tolower((unsigned char)character) >= 'n')
changed = character - 'n' + 'a';
else
changed = character + 'n' - 'a';
return changed;
}

Still only works for some character sets, of course.

The argument to tolower() is cast to unsigned char because a 'char'
can be negative, while tolower() & co expect characters to be in
the range of 'unsigned char'.
 
A

Arthur J. O'Dwyer

This is a little function I wrote, inspired by the thread
"Urgent HELP! required for Caesar Cipher PLEASE"

$ cat /home/keisar/bin/c/ymse/rot13.h

Something nobody else has pointed out yet: Executable code
like the function below should *not* be in a header file (ending
with ".h", as you have above). It should be in a separate
translation unit, in a source file ending with ".c", and you
should learn how to use your compiler to compile projects
consisting of multiple ".c" source files.
<OT> Using gcc, it's easy:
% gcc -W -Wall -ansi -pedantic -O2 mainfile.c rot13.c
(and any other source files in the project). All the options
are only there to catch mistakes in your code; if you write
perfect code, you don't need them. ;-) [In other words, you
*do* need them. Always.]
char rot13(char character)
{
int changed;
changed = character - 'a' + 'n';
return changed;
}

I find two things strange about this code:
1) I don't have to specify that b should be replaced by n,
c by o and so on. How come?

Because your system, like most systems in the world today,
uses ASCII to represent characters. Part of the ASCII character
table looks like this:

"...]^_`abcdefghijklmnopqrstuvwxyz{|}..."

See how all the lowercase letters are packed together, in order?
That's why your code works (do the math yourself as to why it
converts 'b' to 'o').
As Joona notes, all your code is doing is adding 'n'-'a', or 13
(assuming ASCII), to the character it receives. Which is why it
fails miserably to convert 'o' to 'b', or 'z' to 'a'.
Your code won't work on some other real-life systems out there,
and it might even cause demons to fly out of your nose on the
Death Station 9000. (Google for it.) So it's not really the
best way to do it if you're writing code that's supposed to work
everywhere.
2) The function returns a char(char rot13), but changed
is an integer. How is that possible?

You wrote 'int' instead of 'char', that's how. :) In C,
characters are treated just like little integers, so you can
do arithmetic on them, as you've already figured out. And of
course you can assign 'char' to 'int' and vice versa, because
that just involves narrowing or widening the integers involved.

Here's how you could write that code more portably, so it
wouldn't depend on the organization of the letters in your
character set:


#include <ctype.h>

int rot13(int c)
{
static char lookup[UCHAR_MAX] = {0};
static char Alpha[] = "abcdefghijklmnopqrstuvwxyz";
static int not_initialized_yet = 1;

if (not_initialized_yet) {
unsigned int i;
for (i=0; i < sizeof lookup; ++i) {
lookup = i;
}
for (i=0; i < sizeof Alpha; ++i) {
lookup[Alpha] = Alpha[(i+13) % 26];
lookup[toupper(Alpha)] = toupper(Alpha[(i+13) % 26]);
}
not_initialized_yet = 0;
}

return lookup[c];
}


Doesn't that look complicated, now? But note that most of the
time -- *all* the time after the first time you call the function --
it doesn't even need to do any arithmetic! It's just a simple
table lookup, plus some complicated stuff to initialize the table.

I changed 'char' to 'int' to bring 'rot13' in line with similar
standard functions like 'toupper', which take and return 'int'.
As you've found out, 'char' is *almost* always replaceable by 'int'
(one big exception being text strings, obviously).

HTH,
-Arthur
 
L

Larry Doolittle

Arthur said:
<OT> Using gcc, it's easy:
% gcc -W -Wall -ansi -pedantic -O2 mainfile.c rot13.c
(and any other source files in the project). All the options
are only there to catch mistakes in your code; if you write
perfect code, you don't need them. ;-) [In other words, you
*do* need them. Always.]
</OT>

My biggest complaint about gcc -W -Wall is that it barks at unused
parameters. If someone tells me how to make

int foo(void);
int bar(char*cd)
{
foo();
return 0;
}

standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi -std=c99"
without warnings, I'd like to hear about it. My default set of
flags to gcc now also includes
-Wpointer-arith -Wcast-qual -Wshadow

- Larry
 
L

Larry Doolittle

<OT> Using gcc, [the mandatory flags are]:
% gcc -W -Wall -ansi -pedantic -O2 mainfile.c rot13.c
</OT>

My biggest complaint about gcc -W -Wall is that it barks at unused
parameters. If someone tells me how to make [a perfectly good c program]
standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi -std=c99"
without warnings, I'd like to hear about it.

Sorry, guys, somehow I thought the fix might be _inside_ the C program.
But just a _little_ more experimentation taught me that there is really
nothing wrong with the program, and I just need to add -Wno-unused-parameter
to the gcc options string. So this whole thing is still <OT>, although
I had myself confused enough I didn't realize it.

- Larry
 
E

Eric Sosman

Larry said:
<OT> Using gcc, it's easy:
% gcc -W -Wall -ansi -pedantic -O2 mainfile.c rot13.c
(and any other source files in the project). All the options
are only there to catch mistakes in your code; if you write
perfect code, you don't need them. ;-) [In other words, you
*do* need them. Always.]
</OT>

My biggest complaint about gcc -W -Wall is that it barks at unused
parameters. If someone tells me how to make

int foo(void);
int bar(char*cd)
{
foo();
return 0;
}

standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi -std=c99"
without warnings, I'd like to hear about it. My default set of
flags to gcc now also includes
-Wpointer-arith -Wcast-qual -Wshadow

I've had success with

int bar(char *cd) {
(void)cd;
foo();
return 0;
}

Of course, the Standard provides no way to prevent the compiler
from issuing whatever warnings it feels like. If it objects to
the color of your socks it's free to say so, so long as it
accepts and runs an otherwise conforming program despite the
sartorial cluelessness of the coder.
 
M

Martin Dickopp

Larry Doolittle said:
My biggest complaint about gcc -W -Wall is that it barks at unused
parameters. If someone tells me how to make

int foo(void);
int bar(char*cd)
{
foo();
return 0;
}

standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi -std=c99"
without warnings, I'd like to hear about it.

This is not standard conforming, but usually close enough in practice:


#ifdef __GNUC__
# define unused __attribute__ ((unused))
#else
# define unused
#endif

int foo(void);
int bar(char*cd unused)
{
foo();
return 0;
}


(BTW, `-ansi' is an alias for `-std=c89', so it makes little sense specify
both `-ansi' and `-std=c99'.)

Martin
 
C

CBFalconer

Larry said:
.... snip ...

My biggest complaint about gcc -W -Wall is that it barks at unused
parameters. If someone tells me how to make

int foo(void);
int bar(char*cd)
{
foo();
return 0;
}

standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi
-std=c99" without warnings, I'd like to hear about it. My default
set of flags to gcc now also includes
-Wpointer-arith -Wcast-qual -Wshadow

Rewrite it as:

int foo(void);
int bar(void)
{
foo();
return 0;
}

and you will get no warnings :) You will also save the overhead
of passing unused parameters in each call. You should also be
using -pedantic.
 
L

Larry Doolittle

Larry said:
... snip ...
If someone tells me how to make

int foo(void);
int bar(char*cd)
{
foo();
return 0;
}

standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi
-std=c99" without warnings, I'd like to hear about it. [chop]

Rewrite it as:

int foo(void);
int bar(void)
{
foo();
return 0;
}

and you will get no warnings :) You will also save the overhead
of passing unused parameters in each call.

The point is that bar(char *) is defined to fit into a
general interface. Other functions are defined that _do_
use their parameters, and function pointers to both bar
and these other functions are passed around or stored in
tables. These function pointers have to all have the
same type.

Even a contrived example would take more space than I think
people want to wade through on this newsgroup. For a widely
used example, just look at <OT> POSIX sigaction subsystem,
in particular the sa_sigaction function said:
You should also be using -pedantic.

I do. And as pointed out elsethread, I "fixed" the (gcc-specific)
warnings by adding the (gcc-specific) "-Wno-unused-parameter" flag.
Interestingly, this adjustment is not needed with either "-W" or
"-Wall", only with both. In retrospect, I might have been able
to deduct that from the man page.

- Larry
 
A

Alex

Larry Doolittle said:
Larry said:
... snip ...
If someone tells me how to make

int foo(void);
int bar(char*cd)
{
foo();
return 0;
}

standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi
-std=c99" without warnings, I'd like to hear about it. [chop]

Rewrite it as:

int foo(void);
int bar(void)
{
foo();
return 0;
}

and you will get no warnings :) You will also save the overhead
of passing unused parameters in each call.
The point is that bar(char *) is defined to fit into a
general interface. Other functions are defined that _do_
use their parameters, and function pointers to both bar
and these other functions are passed around or stored in
tables. These function pointers have to all have the
same type.
Even a contrived example would take more space than I think
people want to wade through on this newsgroup. For a widely
used example, just look at <OT> POSIX sigaction subsystem,
in particular the sa_sigaction function </OT>.

If this is the case, then it is far better to explicitly
state your intention to disregard the parameter by casting
it to void. If you find that you have to do this most of
the time then the design of your general interface could
use some work.

Alex
 
C

CBFalconer

Alex said:
Larry Doolittle said:
Larry Doolittle wrote:

... snip ...
If someone tells me how to make

int foo(void);
int bar(char*cd)
{
foo();
return 0;
}

standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi
-std=c99" without warnings, I'd like to hear about it. [chop]

Rewrite it as:

int foo(void);
int bar(void)
{
foo();
return 0;
}

and you will get no warnings :) You will also save the overhead
of passing unused parameters in each call.
The point is that bar(char *) is defined to fit into a
general interface. Other functions are defined that _do_
use their parameters, and function pointers to both bar
and these other functions are passed around or stored in
tables. These function pointers have to all have the
same type.
.... snip ...

If this is the case, then it is far better to explicitly
state your intention to disregard the parameter by casting
it to void. If you find that you have to do this most of
the time then the design of your general interface could
use some work.

If he needs to drive a family of functions via something like a
pointer array, and only one needs a parameter, all must receive
such. Ignoring my earlier somewhat facetious reply, it is also
possible to use the parameter and let the compiler optimize that
use out. The results will depend on the compiler. The existance
of a warning does not indicate an error, it simply indicates that
the programmer should know what is going on.

int foo(int bar)
{
bar;
return 0;
}
 
L

Larry Doolittle

I don't understand this comment.
If he needs to drive a family of functions via something like a
pointer array, and only one needs a parameter, all must receive
such.

This is, in fact, my situation. I find many cases where
table-driven code is shorter to write and faster to execute
than equivalent written-out code, even with the occasional
superfluous parameter.
Ignoring my earlier somewhat facetious reply, it is also
possible to use the parameter and let the compiler optimize that
use out. The results will depend on the compiler. The existance
of a warning does not indicate an error, it simply indicates that
the programmer should know what is going on.

Yes, but warnings that the knowledgable programmer cannot
suppress (with good code) are _bad_, because (as I suspect
you also believe) they reduce the emotional impact of new
and possibly dangerous warnings. Such warnings can appear
when code is modified (i.e., maintained), or compiled in a
different environment with different header files.

When I try to "use" a parameter as CBFalconer suggested,
gcc barks in a different way:
warning: statement with no effect
I can in turn fix that by modifying the "use" the statement to:
(void) cd;
Is this what Alex meant, above?

- Larry
 
A

Alex

I don't understand this comment.

When I try to "use" a parameter as CBFalconer suggested,
gcc barks in a different way:
warning: statement with no effect
I can in turn fix that by modifying the "use" the statement to:
(void) cd;
Is this what Alex meant, above?

Yes, that is indeed what I was talking about. This doesn't just
shut up the compiler, either. It makes it easier for the
programmer maintaining your code to see that you really didn't
care about the parameter.

Alex
 
D

David Resnick

Larry Doolittle said:
<OT> Using gcc, [the mandatory flags are]:
% gcc -W -Wall -ansi -pedantic -O2 mainfile.c rot13.c
</OT>

My biggest complaint about gcc -W -Wall is that it barks at unused
parameters. If someone tells me how to make [a perfectly good c program]
standards-conforming, non-bloated, and run past "gcc -W -Wall -ansi -std=c99"
without warnings, I'd like to hear about it.

Sorry, guys, somehow I thought the fix might be _inside_ the C program.
But just a _little_ more experimentation taught me that there is really
nothing wrong with the program, and I just need to add -Wno-unused-parameter
to the gcc options string. So this whole thing is still <OT>, although
I had myself confused enough I didn't realize it.

- Larry

This discussion has gotten rather off topic, but...

An alternative way to do this is the following. It will fix your
problem with gcc, and won't do any harm with other compilers as far as
I can see. It requires that you intentionally state that the
parameter is not used, not a bad thing since that COULD indicate an
error rather than an interface over which you have no control. And it
has the benefit of documenting that the parameter is not used by the
function...

#include <stdio.h>

#ifdef __GNUC__
#define UNUSED __attribute__((unused))
#else
#define UNUSED
#endif


int foo(void)
{
printf("You've got foo!\n");
return 0;
}

int bar(char *cd UNUSED)
{
foo();
return 0;
}

int main(void)
{
char s[] = "hi there";
bar(s);
return 0;
}

gcc -W -Wall -ansi -std=c99 -pedantic -o foo foo.c
gives no complaints on the above...

-David
 
R

Randy Howard

#include <stdio.h>

#ifdef __GNUC__
#define UNUSED __attribute__((unused))
#else
#define UNUSED
#endif

This works for the simple case where the code simply never
used the parameter, in which case I would prefer to change the
interface if that is a possibility. However, where I usually
see this is in code that has conditional compilation, such
that some build options use the parameter and others do not.

Example, writing code to portably handle something (behind some
#ifdef platform stuff) that uses the parameter in some cases,
but not in others. You can still accomplish that with some
ugly conditional code in function prototypes and implementation
using a combination of the conditional platform logic and the
above, but it gets real ugly, real fast.

A simpler option for gcc (which the above only handles anyway)
is to use the -Wno-unused-parameter flag and not have to
bury strange looking UNUSED incantations throughout your
implementation. Much better to modify the makefile than the
source (IMO) just to quiet a minor warning.
 

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,776
Messages
2,569,603
Members
45,189
Latest member
CryptoTaxSoftware

Latest Threads

Top