2

I'm trying to replicate some printf functionality for education purposes, but I've encountered some printf behavior that I'm unable to understand. I'll try explaining with a simple example:

have this call:

printf(" %c %c %c", 0, 1, 2); // yes, parameters are ints not chars.

The output seems normal, only 3 spaces, numbers are ignored.

But taking printf output to a file, then using "cat -e file" does this:

 ^@ ^A ^B

^@ for 0, ^A for 1, ^B for 2 and so on.

Here is my question, what are those symbols? How they relate to the values?

Also my own printf, does this as well except for the 0 which is treated as a '\0' char...I need to mimic printf exactly so I need to understand what is going on there...

I've searched about those symbols, but can't find anything. They are not memory garbage because the results are always same.

coco
  • 19
  • 6
Lineath
  • 33
  • 4
  • 2
    Note that the numbers are not "ignored", they just happen to be non-printable. – Bob__ Jan 27 '23 at 16:55
  • 1
    You asked to print the characters with ASCII codes `0`, `1`, and `2`. These are control characters, so they don't produce any output on the terminal. But they're still written to stdout, and when you redirect to the file they're stored in the file. `cat -e` shows control characters visibly. – Barmar Jan 27 '23 at 16:56
  • For comparison, try `printf("%c %c %c", 65, 66, 67);`. – Steve Summit Jan 27 '23 at 17:12
  • So why is 0 treated as a ^@ non printable character, but when i print (using write()) is treated as a '\0' char and ends the string abruptly? , non 0 unmbers are treated fine, its just the zero. – Lineath Jan 27 '23 at 17:23
  • 2
    The 0 or NUL character, which cat -e shows as ^@, is special in that it's the *null character* which terminates strings in C. So if you used `sprintf` to create a string (instead of `printf` to print to stdout), you'd seem to see the string being truncated, because it would end at the ^@. And if you read a string containing ^@ from a file — perhaps using `fgets — it will also seem to be truncated. – Steve Summit Jan 27 '23 at 17:25
  • But, yes, the 0 *is* the \0 char. You need to say a little more about how this is causing you problems. – Steve Summit Jan 27 '23 at 17:27
  • I know its the null character. What i meant is why printf prints ^@ while im getting '\0'. I'm using memset to replace the character. – Lineath Jan 27 '23 at 17:30
  • Also note that `'\0'` is the terminator of *strings*, but it's still a "valid" `char`: https://godbolt.org/z/3jKc88seW – Bob__ Jan 27 '23 at 17:43
  • 1
    BTW, characters _are_ integers. The difference is that things like `%d` tell `printf()` to perform an int-to-text conversion, turning things like `12` into `"12"`. The `%c` format specifier does not do that; it just puts the integer on output without the to-text conversion (and with a limited range, usually 8-bits maximum). – Dúthomhas Jan 27 '23 at 17:54
  • @Lineath "Also my own printf, does this as well except for the 0 which is treated as a '\0' char..." --> Please post the sample that fails for 0. – chux - Reinstate Monica Jan 27 '23 at 18:03
  • *Also my own printf, does this as well except for the 0 which is treated as a '\0' char* It's still not clear what you mean by this. I think you're saying that your printf is behaving differently from the standard `printf` in some respect. But you haven't really explained that difference, and you haven't shown us your printf code, so we can't guess. – Steve Summit Jan 27 '23 at 18:03

4 Answers4

4

The -e option to cat tells it to use a particular notation for printing non-printable characters. ^@ represents the value 0, ^A represents the value 1, and ^B represents the value 2. Those are exactly the values you gave.

dbush
  • 205,898
  • 23
  • 218
  • 273
4

Simply cat uses caret notation to display not printable characters. ^A represents 1 and ^Z represents 26. ^@ is 0 and

  • 27 - ^[
  • 28 - ^\
  • 29 - ^]
  • 30 - ^^
  • 31 - ^_
  • 127 - ^?
coco
  • 19
  • 6
0___________
  • 60,014
  • 4
  • 34
  • 74
1

What i meant is why printf prints ^@ while im getting '\0'(?)

printf("%c", 0); prints the null character to stdout. What you see when viewing the output of stdout is not part of stdout, but an implementation detail of the shell/terminal program. Print the return value of printf() to get its understanding of how many characters it printed. Perhaps on stderr to separate it from stdout.

int count = printf(" %c %c %c", 0, 1, 2);
fprintf(stderr, "<%d>\n", count);

The output seems normal, only 3 spaces, numbers are ignored.

"Seems" deserves more detail. How was only 3 determined?

But taking printf output to a file, ...

What was the length of the file? 6?

... then using "cat -e file" does this:

Refer to @dbush good answer as to why you now see " ^@ ^A ^B".


I'm using memset to replace the character.

is unclear as there is no memset() in the question.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

Got it solved. Thanks to the explanations here, i realized that even i was printing the resulting string with write() i was using a pointer to iterate it so never gave a chance to actually pass over that null character.

write(STDOUT_FD, delta, d_length);

Then write() does the job correcty:

make test > check.txt && cat -e check.txt
own: ^@ ^A ^B$
lib: ^@ ^A ^B$

Also now i know about the caret notation, thanks everyone!

Lineath
  • 33
  • 4
  • `sizeof(char)` is always 1 by definition. – Andrew Henle Jan 27 '23 at 19:09
  • Why not just `write(STDOUT_FD, delta, d_length)`? – Steve Summit Jan 27 '23 at 19:09
  • sizeof its just a good practice i was told to do, for compatibility. – Lineath Jan 27 '23 at 19:13
  • About @SteveSummit, yep! i was using write() for weeks to print single chars, so i in my head was only for that! thanks! – Lineath Jan 27 '23 at 19:20
  • @Lineath I thought it might have been something like that. Calling `write` to write one character at a time is sort of like riding a bicycle with one pedal. You have to keep stopping, and pulling the pedal back up to the top, so you can push it through another half turn. It can be done, but it's super, frustratingly, agonizingly inefficient! :-) – Steve Summit Jan 27 '23 at 19:29
  • `sizeof delta[0]` is a good practice to express the sizeof what `delta` points to. `sizeof(char)` is better a `1`. – chux - Reinstate Monica Jan 27 '23 at 20:12
  • "i realized that even i was printing the resulting string with write() i was using a pointer to iterate it so never gave a chance to actually pass over that null character." --> that makes little sense as using a pointer to iterate is no the problem. What was the true errant code? – chux - Reinstate Monica Jan 27 '23 at 20:15