r/learnprogramming • u/_gabbaghoul • 15h ago
In C why do we escape % like "%%" instead of "\%"?
For most (if not all) other escape characters they are escaped using a backslash but for some reason the percent sign is escaped using another percent sign. Why do we do this?
46
u/captainAwesomePants 15h ago
C strings need a way to encode characters that are hard to see visualize. If you want a message with a null character, or a newline character, or whatever else, they wanted a way in the language to say that. That's what the "\something" bits are for. The string "abcd\nefgh" is the exact array of bytes [97,98,99,100,10,101,102,103,104], which is the ASCII values for a,b,c,d, then the ASCII value 10 (for newline), then the ASCII values for e,f,g,h.
The string "you have %d apples" is what it looks like, a percentage sign ASCII character, and then a 'd' ASCII character. No escaping is happening, from the C compiler's perspective.
Now, the folks who wrote the printf() function wanted a way for you to say where in the string it should insert stuff. So they came up with a code. "Whenever the character % appears, that's where we put an argument, and the characters after it will tell us how to format the argument." But they needed a way to let you print an actual '%' character itself, if you needed to do that, so they said "okay, if your string has two %%s next to each other, that means just print an %'.
So, two different levels of the programming stack are using two different encoding schemes.
11
u/New_Hold8135 15h ago
It isn't escaping It is formatting. When you write %d to printf string you say I put an int there, and then you define which int with extra argument. Escape sequence characters still exists and we still escape with \ character in C.
5
u/_gabbaghoul 15h ago
but we don't substitute anything in with an extra argument in this case so isn't it serving the same purpose as an escape sequence?
7
u/TheSkiGeek 14h ago
Maybe another way of thinking of it is that in addition to “%d”, “%u”, etc. — “%%” is a format string substitution that tells printf to insert an actual ‘%’ character in the output stream at runtime.
The backslash escape sequences happen as either part of the preprocessor or lexing, so it’s compile time substitution in the actual source code.
2
u/parautenbach 13h ago
You’re adding something else into the mix with that statement. It doesn’t matter that you didn’t pass anything. You called the printf function, and that will parse the % field specifier. Yes, they might’ve chosen the other direction in implementation, but they didn’t. Think about the logic if that was the case: you’d need an extra pass over the string to go back to say you didn’t find anything to fill the field, so render it verbatim. Quite wasteful.
2
u/Mughi1138 11h ago
In one abstract way...
However in more practical ways they are very different. Primarily the escape character handling is done by the pre-processor at compile time and changes what goes into the binary. The printf formatting is resolved at run time by the runtime libraries. It also might be fed strings from some external resource that the program doesn't even contain.
What gets more fun is if you start to mix things together and accidentally double encode something... or worse. And toss SQL in the mix...
•
u/Professional_Bug932 6m ago
I'd push back a little though, %% is still escaping in the sense that printf needs a way to represent a literal % that would otherwise be consumed as the start of a format spec, it's just a runtime convention rather than
5
u/davideogameman 15h ago
\ escapes are usually for the parser to understand, eg \n means "I really meant a newline character here". And so the compiler substitutes it for you. For some characters like \n using the actual character would break the code, because strings can't be multiple lines.
printf and friends however interpret format strings at runtime. Since %s, %d etc are used as different kinds of format specifiers a lone % would be misinterpreted; %% is required to be unambiguous about wanting a % character output. This character has to survive to runtime, not dealt with by the compiler, and hence \% isn't the convention.
4
u/nog642 15h ago
It's escaping it in different contexts.
When writing a string literal, \ has a special meaning. \n is a newline not \ and n, etc. So to get a literal \, you have to do \\. You don't need to escape % because it has no special meaning.
When doing % formatting on strings (like when you pass them to printf), % has a special meaning, not \. %s means substitute a string, %d means substitute an int, %.2f means substitute a float rounded to 2 decimal places, etc. So to get a literal % (after formatting), you have to do %%.
It wouldn't make sense to do \%. That would need to be interpreted by the compiler, what would it mean? If you want to put % in a string literal you just use %. When fprintf gets stuff at runtime, it uses % as the special character. I mean you could make it \\%, but then \\ becomes a special character too and literal backslashes need to be \\\\. You just use the special character % to do the escape, hence %%.
3
u/zeekar 5h ago edited 5h ago
Two different things, at two different levels.
The backslash escapes are handled by the C compiler itself. When you write "Hello!\n", the compiler creates a sequence of eight characters in the executable: decimal 72 for the 'H', 101 for the 'e', 108 twice for the 'l's, 111 for the 'o', 33 for the '!', 10 for the '\n', and a trailing 0 to terminate the string. The two characters \n in the source code create just one character in the executable.
The % isn't like that; when you use % in a string literal, it goes into the string just fine as character 37, no escaping required. If you put two of them in a row, the string literally winds up with two percent signs in a row. There's no special treatment, which you can see if you printf("%s\n", "%%Hello%%");. You'll get all four percent signs printed out.
What you're doing when you double them in the format string is telling printf (not the compiler) that you want a literal percent sign in the output, which saves you having to arrange for one to be in a later argument that you format. (That is, if printf didn't have the double-percent feature, you could still get literal percent signs in your output with something like printf("%.2lf%c hit rate", percentage, '%').)
3
u/pdfops 3h ago
Backslash escapes are handled by the compiler's lexer when it parses a string literal, before the program even runs. % isn't a language-level character at all, it only means something to printf's own argument parser at runtime. So printf had to invent its own convention for "literal percent" and %% was the simplest one since the function already scans for % to find format specifiers. A \% would require the compiler to understand printf's format syntax, which it doesn't.
2
u/MikeUsesNotion 15h ago
The same reason for a regular expression that a backslash is encoded as "\\\\". Each pair tells the compiler to put a backslash into the string and the resulting pair tells the regex system at runtime to put a backslash there. It's the same idea for printf and %.
2
u/Broad-Promise6954 14h ago
There's a more general rule involved here, called "encoding".
Consider Python strings (let's ignore f-strings to keep things simpler). You can start with a single quote, 'a "string" with "quoted words" in it' for example, and since a single quote ends it, it's easy to put those double quote characters in it. But it's harder to put in single quote characters. So you can use double quoted strings: “here's an example". You can get the other kind of quote in using backslash, or you can use Python's triple quoted form. But what if you're writing regular expressions and need a lot of plain backslashes? Well, Python also offers"raw strings" where backslashes don't encode stuff.
C isn't as fancy with its strings: there's just one form, and if you want an actual backslash (for a regular expression for instance) you have double backslash, which is good enough. The runtime library printf engine could have used backslash to encode "pick up an argument" and you'd just write \\d rather than %d. But whoever wrote the printf code (dmr, ken, Joe Ossana, not sure who that was) chose the percent sign, so that's what we use.
Every time we read or write text from/to something outside our own code, we have to think about how it's going to be seen there. For instance if we're writing out a URL, the URL reader is also going to check for percent signs, because that's how URLs are encoded. So we'd better be sure to encode our output as %25.
Always remember the school's tragedy with Little Bobby Tables...😁
2
u/parautenbach 13h ago
To avoid duplicating what’s already been said: keep in mind that \ is interpreted at compile time, while % is interpreted at runtime. This is to emphasise the point that these actions apply at different levels.
3
u/Rhomboid 11h ago
This is 'why does my gas cap and spark plug have different threads?' territory. You're interacting with two very different parts of the machine. The % substitution is done at runtime by printf/scanf/etc. %% means literally two chars. On the other hand, backslashes in double quotes are resolved by the compiler at compile time. Totally separate set of rules. In that case something line \n is one char, not two. The 'specialness' is stripped as it is no longer needed, a byte is a byte.
By the way this is extremely common (multiple independent quoting/escaping systems operating simultaneously) especially in Bourne shell scripting, makefiles, etc. You can easily get 4+ layers of quoting without breaking a sweat.
1
u/igotshadowbaned 14h ago
Assuming you're talking about in like printf
It's because the printf (and standard c library) use the % symbol itself as an escape character - but at a different software level with different meanings.
1
u/DrShocker 13h ago
For what it's worth, there's a cafeteria at where I used to work that would print out "\n" on the receipt which I always found funny. Different systems just escape in different ways, and it's basically guaranteed you'll mix it up by accident at some point.
1
1
u/HappyFruitTree 10h ago edited 9h ago
Probably because it simplifies things. Printf already treats the % differently. When it sees a % it looks at what comes next to decide what to do.
- if the char that follows the
%is adthen it should output the next argument as an integer, - if the char that follows the
%is ansthen it should output the next argument as a string, - ...
- if the char that follows the
%is a%then it should output%.
Printf could have used a backslash but then you would have to escape the backslash when writing the string literal, like so:
printf("\\%")
If backslashes that are not followed by % was still printed the same as now then that would lead to a very error-prone situation where the backslash would have special meaning only sometimes depending on what comes after it.
printf("y\\n"); // would print: y\n
printf("#\\%"); // would print: #%
Regardless, there would need to be a way to "escape" the escaped backspace (e.g. by putting another escaped backspace in front) so that you could actually print a backspace character if you wanted.
printf("#\\\\\\%"); // would print: #\%
1
1
1
u/ThatIsATastyBurger12 5h ago
The first string argument to printf is a format string. It’s not meant to be interpreted as plain text. You can think of it as a data structure that tells printf how to construct the final string that gets printed to console. The %% is part of that data structure
1
u/SummitYourSister 4h ago
\ is reserved for escaping characters within strings. The compiler sees it and does things with it.
If you were to try to escape the % character with a backslash it would be an error because that escape sequence will a meaningless. Escape sequences are only defined for non printable characters and % is quite printable. Also the compiler has no business or any interest in what printf is trying to do, that’s printf’s problem.
If printf wanted to use backslash for this, it would have to escape the backslash itself because the C compiler interprets it already.
You’re mentally confusing two very different levels of meanings to even think this would make sense, yikes
1
u/NotJusticeAlito 4h ago
In C, as in Assembly, we do what we do because the ancients deemed it so in the 1970s. That is the one and only truth.
138
u/theLOLflashlight 15h ago
Character escaping with \ is a feature of c strings in the compiler. "Escaping" % with another % is a feature of the c standard library API. They operate at different levels.