In-depth understanding of sprintf function based on C language

printf may be the second function that many programmers come into contact with when they start learning the C language (I guess the first one is main). Speaking of which, it is naturally an old friend. However, do you know more about this old friend? Do you know much about its twin brother, sprintf? The power of sprintf will rarely let you down when it comes to structuring various types of data into strings.
Since sprintf is almost the same as printf in usage, only the destination of printing is different. The former is printed to a string, and the latter is output directly on the command line. This also makes sprintf much more useful than printf. So this article focuses on sprintf, sometimes interspersed with pritnf.
sprintf is a variable parameter function, which is defined as follows:
int sprintf( char *buffer, const char *format [, argument] … );
In addition to the fixed types of the first two parameters, any number of parameters can be followed. And its essence is obviously in the second parameter: the format string.
Both printf and sprintf use a format string to specify the format of the string. Inside the format string, some format specifiers (format specifications) beginning with "%" are used to occupy a position, and the corresponding variables are provided in the following variable parameter list. , the final function will replace that specifier with the variable in the corresponding position, producing a string that the caller wants.
1. Formatting numeric strings
One of the most common applications of sprintf is to print integers into strings, so spritnf can replace itoa in most cases. Such as:
//Print the integer 123 as a string and save it in s.
sprintf(s, "%d", 123); //Generate "123"
to specify the width, and fill the space on the left side:
sprintf(s, "%8d%8d", 123, 4567); //Generate: "123 4567" , of
course, can also be left-aligned:
sprintf(s, "%-8d%8d", 123, 4567); //Generate : "123 4567"
can also be printed in hexadecimal:
sprintf(s, "%8x", 4567); //lowercase hexadecimal, the width occupies 8 positions, right-aligned
sprintf(s, "%-8X", 4568
; 0 in the monospace format, what should I do? Very simple, just add a 0 in front of the number representing the width.
sprintf(s, "%08X", 4567); //Generate: "000011D7"
The decimal printing with "%d" above can also use this method of padding 0 on the left.
There is a sign extension problem to pay attention to here: for example, if we want to print the memory hexadecimal representation of a short integer (short)-1, on the Win32 platform, a short type occupies 2 bytes, so we naturally want to use 4 hexadecimal digits to print it:
short si = -1;
sprintf(s, "%04X", si);
produces "FFFFFFFF", what's going on? Because spritnf is a variable parameter function, except for the first two parameters, the latter parameters are not type-safe, and there is no way for the function to know that the parameters were pushed on the stack before the function call just through a "%X". Is it a 4-byte integer or a 2-byte short integer, so a unified 4-byte processing method is adopted, resulting in sign extension when the parameter is pushed onto the stack, and expanded to a 32-bit integer -1. 4 positions are not enough, so the 8-bit hexadecimal of the 32-bit integer -1 is printed out. If you want to see si as it is, then you should ask the compiler to do 0 extension instead of sign extension (the left side of the binary is 0 instead of the sign bit):
sprintf(s, "%04X", (unsigned short)si );
just fine. Or:
unsigned short si = -1;
sprintf(s, "%04X", si);
sprintf and printf can also print integer strings in octal, using "%o". Note that neither octal nor hexadecimal will print negative numbers, they are all unsigned, which is actually the direct hexadecimal or octal representation of the variable's internal encoding.
2. Control the printing format
of floating-point numbers The printing and format control of floating-point numbers is another common function of sprintf. Floating-point numbers are controlled by the format character "%f", and 6 digits after the decimal point are reserved by default, for example:
sprintf(s, “% f", 3.1415926); //Generate "3.141593"
but sometimes we want to control the width and decimal places of printing, then we should use: "%m.nf" format, where m represents the width of printing, and n represents the decimal point digits after. For example:
sprintf(s, "%10.3f", 3.1415626); //Generates: "3.142"
sprintf(s, "%-10.3f", 3.1415626); // yields: "3.142"
sprintf(s, "%.3f", 3.1415626); // does not specify the total width, yields: "3.142"
Note a problem, Guess what
int i = 100;
sprintf(s, "%.2f", i);
will print? "100.00"? Is it right? Just try it yourself, and also try the following:
sprintf(s, “%.2f”, (double)i);
The first one is definitely not the correct result, the reason is the same as mentioned above, the parameter When the stack is pushed, the caller does not know that the format controller corresponding to i is "%f". When the function is executed, the function itself does not know that what was pushed into the stack was an integer that year, so the poor 4 bytes that hold the integer i are forced to be interpreted as a floating-point number format without any explanation, and the whole mess is messed up.
However, if anyone is interested in hand-coding a floating-point number, you can use this method to check that your hand-coded result is correct. J
character/Ascii code comparison
We know that in the C/C++ language, char is also a common scalable type. Except for the word length, it has no essential difference from short, int, and long, but it is used by everyone. to represent characters and strings only. (Perhaps this type should be called "byte" back then, and now you can use byte or short to define char through typedef according to the actual situation, which is more appropriate)
So, use "%d" or "%x" to print a character, you can get its decimal or hexadecimal ASCII code; conversely, use "%c" to print an integer, you can see it The corresponding ASCII character. The following program segment prints the ASCII code comparison table of all visible characters to the screen (printf is used here, note that when "#" and "%X" are used together, the "0X" prefix is ​​automatically added to the hexadecimal number):
for(int i = 32; i < 127; i++) {
printf(”[ %c ]: %3d 0x%#04X/n”, i, i, i);
}
3.
Since the format control string of the connection string sprintf can insert various sprintf can concatenate multiple strings at once (and of course insert something else between them at the same time) , in short very flexible). For example:
char* who = "I";
char* whom = "CSDN";
sprintf(s, "%s love %s.", who, whom); //Generate: "I love CSDN." strcat can only connect String (a '/0' terminated character array or character buffer, null-terminated-string), but sometimes we have two character buffers, they are not '/0' terminated. For example, many character arrays returned from third-party library functions, and character streams read in from hardware or network transmissions may not have a corresponding '/0' behind each character sequence. to end. If direct connection, whether it is sprintf or strcat will definitely lead to illegal memory operations, and strncat also requires at least the first parameter to be a null-terminated-string, what should we do? We will naturally recall that the width can be specified when printing integers and floating-point numbers, and the same is true for strings. for example:

char a1[] = {'A', 'B', 'C', 'D', 'E', 'F', 'G'};
char a2[] = {'H', 'I', ' J', 'K', 'L', 'M', 'N'};
if:
sprintf(s, "%s%s", a1, a2); //Don't do that!
Nine times out of ten Something went wrong. Can it be changed to:
sprintf(s, “%7s%7s”, a1, a2);
it’s not much better, the correct one should be:
sprintf(s, “%.7s%.7s”, a1, a2) ;//Generate: "ABCDEFGHIJKLMN"
This can be analogous to "%m.nf" that prints floating-point numbers. In "%m.ns", m represents the occupied width (fill a space when the string length is insufficient, if it exceeds the actual width print), n only represents the maximum number of characters taken from the corresponding string. Usually m is not very useful when printing strings, or the n after the dot is used more. Naturally, you can also take only some characters before and after:
sprintf(s, "%.6s%.5s", a1, a2);//Generate: "ABCDEFHIJKL"
In many cases, we may also want to use these format control characters with The number with the specified length information is dynamic, not statically specified, because in many cases, the program will not know how many characters in the character array need to be taken at run time. This dynamic width/precision setting function is used in sprintf is also taken into account in the implementation of sprintf using "*" to occupy a position that would otherwise require a constant number with a specified width or precision, and again, the actual width or precision can be provided like any other variable being printed, Thus, the above example can become:
sprintf(s, “%.*s%.*s”, 7, a1,

sprintf(s, "%.*s%.*s", sizeof(a1), a1, sizeof(a2), a2);
In fact, the printing characters, integers, floating-point numbers, etc. introduced earlier can dynamically specify those constants Value, for example:
sprintf(s, "%-*d", 4, 'A'); // yields "65"
sprintf(s, "%#0*X", 8, 128); // yields "0X000080 ", "#" produce 0X
sprintf(s, "%*.*f", 10, 2, 3.1415926); // produce "3.14"
4. Print address information
Sometimes when debugging a program, we may want to view some variables or Addresses of members, since addresses or pointers are just 32-bit numbers, you can print them out using "%u" which prints unsigned integers:
sprintf(s, "%u", &i);
but usually people still Prefer to use hex instead of decimal to display an address:
sprintf(s, "%08X", &i);
However, these are indirect methods, and for address printing, sprintf provides a special "%p":
sprintf(s, "%p", &i);
I think it is actually equivalent to:
sprintf(s, "%0*x", 2 * sizeof(void *), &i);
5. Use the return value of sprintf
The return value of the printf/sprintf functions is less noticed, but sometimes useful, spritnf returns the number of characters that were eventually printed into the character buffer by this function call. That is to say, every time after the end of a sprinf call, you don't need to call strlen again to know the length of the result string. Such as:
int len ​​= sprintf(s, "%d", i);
For positive integers, len is equal to the decimal digits of integer i.
The following is a complete example that generates 10 random numbers between [0, 100) and prints them into a character array s, separated by commas.
Copy the code The  code is as follows:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main() {
srand(time(0));
char s[64];
int offset = 0;
for(int i = 0; i < 10; i++) {
offset += sprintf(s + offset, “%d,”, rand() % 100);
}
s[offset - 1] = '/n';//put the last comma Replace it with a newline.
printf(s);
return 0;
}

Imagine that you can use this method when you fetch a record from the database and then want to concatenate their fields into a string according to certain rules. In theory, it should be more efficient than constant strcat, because Each time strcat is called, it needs to find the last '/0' position, and in the example given above, we use the sprintf return value to directly record this position every time.
6. Common problems in using sprintf
sprintf is a variable parameter function, and there are often problems when using it, and as long as there is a problem, it is usually a memory access error that can cause the program to crash. Fortunately, the problem caused by the misuse of sprintf is serious, but very easy To find out, it is nothing more than a few situations. Usually, you can see the wrong code with your eyes and see it a few more times.
?? Buffer overflow
The length of the first parameter is too short, no need to say, give it a bigger place. Of course, it may also be a problem with the following parameters. It is recommended that you must be careful about changing parameters. When printing strings, try to specify the maximum number of characters in the form of "%.ns".
?? Forgot that the first parameter is too low-level to be a low-
level problem, and I am too used to using printf. // Occasionally commits frequently. :. (
?? The problem with variable parameters
is usually that you forgot to provide the variable parameters corresponding to a certain format character, resulting in all the subsequent parameters being misplaced. Check it. Especially those parameters corresponding to "*", are all provided? Don't put An integer corresponds to a "%s", the compiler will think that you cheated her too much (the compiler is the mother of obj and exe, it should be a woman, :P).
7. strftime
sprintf also has a good cousin: strftime, It is specially used to format the time string. The usage is very similar to her cousin, and it is also a lot of format control characters, but after all, the little girl is careful, and she also needs the caller to specify the maximum length of the buffer, which may be for the sake of appearance. When you have a problem, you can pass the buck. Here is an example:
Copy the code The  code is as follows:

time_t t = time(0);
//Generate a string in "YYYY-MM-DD hh:mm:ss" format.
char s[32];
strftime(s, sizeof(s), “%Y-%m-%d %H:%M:%S”, localtime(&t));

sprintf can also find his confidant in MFC: CString::Format, strftime naturally has her counterpart in MFC: CTime::Format, this pair is sponsored by object-oriented, and the code used to write is more Feel elegant.
8. Postscript
All these functions introduced in this article can be easily found in MSDN. The author is just based on his own experience, combined with some examples, some commonly used and useful, but may be unknown to many beginners. The usage has been introduced a little, I hope everyone will not joke, and I hope everyone will criticize and correct.
Some people think that this kind of variadic function will cause various problems, so it is not recommended to use it. But the author himself is often unable to resist the temptation of their powerful functions, and has been using them in practical work. In fact, C#.NET has supported variable parameters from the beginning, and Java 5.0, which has just been released, also supports variable parameters.
①Get System time: void GetSystemTime(LPSYSTEMTIME lpSystemTime); The following is an example:
Copy the code The  code is as follows:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
void main() {
SYSTEMTIME st; //定义存放时间的结构体
char strTime[256];
int n=0;
GetSystemTime(&st);
n = sprintf(strTime,”Year:/t%d/n”,st.wYear);
n += sprintf(strTime+n,”Month:/t%d/n”,st.wMonth);
n += sprintf(strTime+n,”Day:/t%d/n”,st.wDay);
n += sprintf(strTime+n,”Date:/t%d/n”,st.wDayOfWeek);
n += sprintf(strTime+n,”Hour:/t%d/n”,st.wHour);
n += sprintf(strTime+n,”Minute:/t%d/n”,st.wMinute);
n += sprintf(strTime+n,”Second:/t%d/n”,st.wSecond);
n += sprintf(strTime+n,”MilliSecond:/t%d/n”,st.wMilliseconds);
printf(”%s”,strTime);
system(”pause”);
}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324739006&siteId=291194637