国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

C語言printf函數(shù)實(shí)現(xiàn)解讀

這篇具有很好參考價(jià)值的文章主要介紹了C語言printf函數(shù)實(shí)現(xiàn)解讀。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

1 源碼下載

gun官網(wǎng)鏈接
c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源



c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源


c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源



c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源



c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源

按照這些步驟可以順利的下載gnu的c語言源碼,接下去我們看看printf函數(shù)。




2 printf函數(shù)源碼

用vscode打開下載的源碼,找到printf函數(shù)。
printf 函數(shù)路徑為:glibc-2.36\stdio-common\printf.c )
c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源
源碼如下:

int
__printf (const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = __vfprintf_internal (stdout, format, arg, 0);
  va_end (arg);

  return done;
}

#undef _IO_printf
ldbl_strong_alias (__printf, printf);
ldbl_strong_alias (__printf, _IO_printf);

可以看見主要是四個(gè)東西:va_list ? va_start ? va_end ? __vfprintf_internal
前面三個(gè)先不看,后面重點(diǎn)介紹,先看看能不能看懂__vfprintf_internal 這個(gè)函數(shù)的實(shí)現(xiàn)。

c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源

glibc-2.36\stdio-common\vfprintf-internal.c 里可以看到這個(gè)函數(shù)實(shí)際上就是vfprintf函數(shù), 也在相同的.c文件中

c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源

源碼如下:

int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{
  /* The character used as thousands separator.  */
  THOUSANDS_SEP_T thousands_sep = 0;

  /* The string describing the size of groups of digits.  */
  const char *grouping;

  /* Place to accumulate the result.  */
  int done;

  /* Current character in format string.  */
  const UCHAR_T *f;

  /* End of leading constant string.  */
  const UCHAR_T *lead_str_end;

  /* Points to next format specifier.  */
  const UCHAR_T *end_of_spec;

  /* Buffer intermediate results.  */
  CHAR_T work_buffer[WORK_BUFFER_SIZE];
  CHAR_T *workend;

  /* We have to save the original argument pointer.  */
  va_list ap_save;

  /* Count number of specifiers we already processed.  */
  int nspecs_done;

  /* For the %m format we may need the current `errno' value.  */
  int save_errno = errno;

  /* 1 if format is in read-only memory, -1 if it is in writable memory,
     0 if unknown.  */
  int readonly_format = 0;

  /* Orient the stream.  */
#ifdef ORIENT
  ORIENT;
#endif

  /* Sanity check of arguments.  */
  ARGCHECK (s, format);

#ifdef ORIENT
  /* Check for correct orientation.  */
  if (_IO_vtable_offset (s) == 0
      && _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)
      != (sizeof (CHAR_T) == 1 ? -1 : 1))
    /* The stream is already oriented otherwise.  */
    return EOF;
#endif

  if (UNBUFFERED_P (s))
    /* Use a helper function which will allocate a local temporary buffer
       for the stream and then call us again.  */
    return buffered_vfprintf (s, format, ap, mode_flags);

  /* Initialize local variables.  */
  done = 0;
  grouping = (const char *) -1;
#ifdef __va_copy
  /* This macro will be available soon in gcc's <stdarg.h>.  We need it
     since on some systems `va_list' is not an integral type.  */
  __va_copy (ap_save, ap);
#else
  ap_save = ap;
#endif
  nspecs_done = 0;

#ifdef COMPILE_WPRINTF
  /* Find the first format specifier.  */
  f = lead_str_end = __find_specwc ((const UCHAR_T *) format);
#else
  /* Find the first format specifier.  */
  f = lead_str_end = __find_specmb ((const UCHAR_T *) format);
#endif

  /* Lock stream.  */
  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
  _IO_flockfile (s);

  /* Write the literal text before the first format.  */
  outstring ((const UCHAR_T *) format,
	     lead_str_end - (const UCHAR_T *) format);

  /* If we only have to print a simple string, return now.  */
  if (*f == L_('\0'))
    goto all_done;

  /* Use the slow path in case any printf handler is registered.  */
  if (__glibc_unlikely (__printf_function_table != NULL
			|| __printf_modifier_table != NULL
			|| __printf_va_arg_table != NULL))
    goto do_positional;

  /* Process whole format string.  */
  do
    {
      STEP0_3_TABLE;
      STEP4_TABLE;

      int is_negative;	/* Flag for negative number.  */
      union
      {
	unsigned long long int longlong;
	unsigned long int word;
      } number;
      int base;
      union printf_arg the_arg;
      CHAR_T *string;	/* Pointer to argument string.  */
      int alt = 0;	/* Alternate format.  */
      int space = 0;	/* Use space prefix if no sign is needed.  */
      int left = 0;	/* Left-justify output.  */
      int showsign = 0;	/* Always begin with plus or minus sign.  */
      int group = 0;	/* Print numbers according grouping rules.  */
      /* Argument is long double/long long int.  Only used if
	 double/long double or long int/long long int are distinct.  */
      int is_long_double __attribute__ ((unused)) = 0;
      int is_short = 0;	/* Argument is short int.  */
      int is_long = 0;	/* Argument is long int.  */
      int is_char = 0;	/* Argument is promoted (unsigned) char.  */
      int width = 0;	/* Width of output; 0 means none specified.  */
      int prec = -1;	/* Precision of output; -1 means none specified.  */
      /* This flag is set by the 'I' modifier and selects the use of the
	 `outdigits' as determined by the current locale.  */
      int use_outdigits = 0;
      UCHAR_T pad = L_(' ');/* Padding character.  */
      CHAR_T spec;

      workend = work_buffer + WORK_BUFFER_SIZE;

      /* Get current character in format string.  */
      JUMP (*++f, step0_jumps);

      /* ' ' flag.  */
    LABEL (flag_space):
      space = 1;
      JUMP (*++f, step0_jumps);

      /* '+' flag.  */
    LABEL (flag_plus):
      showsign = 1;
      JUMP (*++f, step0_jumps);

      /* The '-' flag.  */
    LABEL (flag_minus):
      left = 1;
      pad = L_(' ');
      JUMP (*++f, step0_jumps);

      /* The '#' flag.  */
    LABEL (flag_hash):
      alt = 1;
      JUMP (*++f, step0_jumps);

      /* The '0' flag.  */
    LABEL (flag_zero):
      if (!left)
	pad = L_('0');
      JUMP (*++f, step0_jumps);

      /* The '\'' flag.  */
    LABEL (flag_quote):
      group = 1;

      if (grouping == (const char *) -1)
	{
#ifdef COMPILE_WPRINTF
	  thousands_sep = _NL_CURRENT_WORD (LC_NUMERIC,
					    _NL_NUMERIC_THOUSANDS_SEP_WC);
#else
	  thousands_sep = _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP);
#endif

	  grouping = _NL_CURRENT (LC_NUMERIC, GROUPING);
	  if (*grouping == '\0' || *grouping == CHAR_MAX
#ifdef COMPILE_WPRINTF
	      || thousands_sep == L'\0'
#else
	      || *thousands_sep == '\0'
#endif
	      )
	    grouping = NULL;
	}
      JUMP (*++f, step0_jumps);

    LABEL (flag_i18n):
      use_outdigits = 1;
      JUMP (*++f, step0_jumps);

      /* Get width from argument.  */
    LABEL (width_asterics):
      {
	const UCHAR_T *tmp;	/* Temporary value.  */

	tmp = ++f;
	if (ISDIGIT (*tmp))
	  {
	    int pos = read_int (&tmp);

	    if (pos == -1)
	      {
		__set_errno (EOVERFLOW);
		done = -1;
		goto all_done;
	      }

	    if (pos && *tmp == L_('$'))
	      /* The width comes from a positional parameter.  */
	      goto do_positional;
	  }
	width = va_arg (ap, int);

	/* Negative width means left justified.  */
	if (width < 0)
	  {
	    width = -width;
	    pad = L_(' ');
	    left = 1;
	  }
      }
      JUMP (*f, step1_jumps);

      /* Given width in format string.  */
    LABEL (width):
      width = read_int (&f);

      if (__glibc_unlikely (width == -1))
	{
	  __set_errno (EOVERFLOW);
	  done = -1;
	  goto all_done;
	}

      if (*f == L_('$'))
	/* Oh, oh.  The argument comes from a positional parameter.  */
	goto do_positional;
      JUMP (*f, step1_jumps);

    LABEL (precision):
      ++f;
      if (*f == L_('*'))
	{
	  const UCHAR_T *tmp;	/* Temporary value.  */

	  tmp = ++f;
	  if (ISDIGIT (*tmp))
	    {
	      int pos = read_int (&tmp);

	      if (pos == -1)
		{
		  __set_errno (EOVERFLOW);
		  done = -1;
		  goto all_done;
		}

	      if (pos && *tmp == L_('$'))
		/* The precision comes from a positional parameter.  */
		goto do_positional;
	    }
	  prec = va_arg (ap, int);

	  /* If the precision is negative the precision is omitted.  */
	  if (prec < 0)
	    prec = -1;
	}
      else if (ISDIGIT (*f))
	{
	  prec = read_int (&f);

	  /* The precision was specified in this case as an extremely
	     large positive value.  */
	  if (prec == -1)
	    {
	      __set_errno (EOVERFLOW);
	      done = -1;
	      goto all_done;
	    }
	}
      else
	prec = 0;
      JUMP (*f, step2_jumps);

      /* Process 'h' modifier.  There might another 'h' following.  */
    LABEL (mod_half):
      is_short = 1;
      JUMP (*++f, step3a_jumps);

      /* Process 'hh' modifier.  */
    LABEL (mod_halfhalf):
      is_short = 0;
      is_char = 1;
      JUMP (*++f, step4_jumps);

      /* Process 'l' modifier.  There might another 'l' following.  */
    LABEL (mod_long):
      is_long = 1;
      JUMP (*++f, step3b_jumps);

      /* Process 'L', 'q', or 'll' modifier.  No other modifier is
	 allowed to follow.  */
    LABEL (mod_longlong):
      is_long_double = 1;
      is_long = 1;
      JUMP (*++f, step4_jumps);

    LABEL (mod_size_t):
      is_long_double = sizeof (size_t) > sizeof (unsigned long int);
      is_long = sizeof (size_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

    LABEL (mod_ptrdiff_t):
      is_long_double = sizeof (ptrdiff_t) > sizeof (unsigned long int);
      is_long = sizeof (ptrdiff_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

    LABEL (mod_intmax_t):
      is_long_double = sizeof (intmax_t) > sizeof (unsigned long int);
      is_long = sizeof (intmax_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

      /* Process current format.  */
      while (1)
	{
#define process_arg_int() va_arg (ap, int)
#define process_arg_long_int() va_arg (ap, long int)
#define process_arg_long_long_int() va_arg (ap, long long int)
#define process_arg_pointer() va_arg (ap, void *)
#define process_arg_string() va_arg (ap, const char *)
#define process_arg_unsigned_int() va_arg (ap, unsigned int)
#define process_arg_unsigned_long_int() va_arg (ap, unsigned long int)
#define process_arg_unsigned_long_long_int() va_arg (ap, unsigned long long int)
#define process_arg_wchar_t() va_arg (ap, wchar_t)
#define process_arg_wstring() va_arg (ap, const wchar_t *)
#include "vfprintf-process-arg.c"
#undef process_arg_int
#undef process_arg_long_int
#undef process_arg_long_long_int
#undef process_arg_pointer
#undef process_arg_string
#undef process_arg_unsigned_int
#undef process_arg_unsigned_long_int
#undef process_arg_unsigned_long_long_int
#undef process_arg_wchar_t
#undef process_arg_wstring

	LABEL (form_float):
	LABEL (form_floathex):
	  {
	    if (__glibc_unlikely ((mode_flags & PRINTF_LDBL_IS_DBL) != 0))
	      is_long_double = 0;

	    struct printf_info info =
	      {
		.prec = prec,
		.width = width,
		.spec = spec,
		.is_long_double = is_long_double,
		.is_short = is_short,
		.is_long = is_long,
		.alt = alt,
		.space = space,
		.left = left,
		.showsign = showsign,
		.group = group,
		.pad = pad,
		.extra = 0,
		.i18n = use_outdigits,
		.wide = sizeof (CHAR_T) != 1,
		.is_binary128 = 0
	      };

	    PARSE_FLOAT_VA_ARG_EXTENDED (info);
	    const void *ptr = &the_arg;

	    int function_done = __printf_fp_spec (s, &info, &ptr);
	    if (function_done < 0)
	      {
		done = -1;
		goto all_done;
	      }
	    done_add (function_done);
	  }
	  break;

	LABEL (form_unknown):
	  if (spec == L_('\0'))
	    {
	      /* The format string ended before the specifier is complete.  */
	      __set_errno (EINVAL);
	      done = -1;
	      goto all_done;
	    }

	  /* If we are in the fast loop force entering the complicated
	     one.  */
	  goto do_positional;
	}

      /* The format is correctly handled.  */
      ++nspecs_done;

      /* Look for next format specifier.  */
#ifdef COMPILE_WPRINTF
      f = __find_specwc ((end_of_spec = ++f));
#else
      f = __find_specmb ((end_of_spec = ++f));
#endif

      /* Write the following constant string.  */
      outstring (end_of_spec, f - end_of_spec);
    }
  while (*f != L_('\0'));

  /* Unlock stream and return.  */
  goto all_done;

  /* Hand off processing for positional parameters.  */
do_positional:
  done = printf_positional (s, format, readonly_format, ap, &ap_save,
			    done, nspecs_done, lead_str_end, work_buffer,
			    save_errno, grouping, thousands_sep, mode_flags);

 all_done:
  /* Unlock the stream.  */
  _IO_funlockfile (s);
  _IO_cleanup_region_end (0);

  return done;
}

現(xiàn)在的水平看懂確實(shí)有一定的難度,于是就不看了。




3 幾個(gè)宏及解讀

前面提到了有幾個(gè)其他的東西,現(xiàn)在我們來看看是什么

va_list

在vs2017下的定義是這樣的:(\Community\VC\Tools\MSVC\14.16.27023\include\vadefs.h


#ifndef _VA_LIST_DEFINED
    #define _VA_LIST_DEFINED
    #ifdef _M_CEE_PURE
        typedef System::ArgIterator va_list;
    #else
        typedef char* va_list;
    #endif
#endif

可以看出大部分情況下他就是 char * 類型,再看看百度的解釋
c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源


va_start

c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源
這是 vs2017下x64的定義,看不了他的實(shí)現(xiàn),那么就總結(jié)一下其他資料的解釋。

這個(gè)宏的作用是:得到一個(gè)指向第一個(gè)可變參數(shù)的指針,也就是“”包裹的字符串后面的第一個(gè)參數(shù)。

看看這個(gè)宏的聲明:

void va_start(va_list ap, last_arg);

ap: 這個(gè)參數(shù)的類型是之前提過的va_list,也就是那個(gè)指向可變參數(shù)列表的指針。
last_arg: 是最后一個(gè)固定參數(shù),在printf函數(shù)中就是那個(gè)“”包裹的字符串。
在介紹他的實(shí)現(xiàn)之前,我們需要先補(bǔ)充三個(gè)知識(shí):

  1. 函數(shù)傳參進(jìn)棧的順序
  2. 棧區(qū)在計(jì)算機(jī)內(nèi)部的地址情況
  3. _INTSIZEOF 宏


函數(shù)參數(shù)進(jìn)棧順序

先說第一點(diǎn),函數(shù)參數(shù)進(jìn)棧的順序
???? 從右往左,依次進(jìn)棧

  func(int a, int b, int c, int d);

如果是這個(gè)函數(shù)的話,那么應(yīng)該是d-> c -> b -> a 這是要了解的第一點(diǎn)。

  void func(char *fmt, ...);

fmt肯定是在這一系列參數(shù)中最低的地址部分。



棧區(qū)在計(jì)算機(jī)內(nèi)部的地址情況

來看這張經(jīng)典的圖:

c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源

棧底是高地址,棧頂是底地址,也就是說先進(jìn)棧的會(huì)是高地址。之前那個(gè)例子中,地址從高到低依次也是 d , c, b, a。
在可變參數(shù)的函數(shù)中,大概就是這種情況:

|——————————————————————————|
|最后一個(gè)可變參數(shù) | ----------------高內(nèi)存地址處
|——————————————————————————|

|——————————————————————————|
|第N個(gè)可變參數(shù) | -----------va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N個(gè)可變參數(shù)的地址。
|——————————————— |
………………………….
|——————————————————————————|
|第一個(gè)可變參數(shù) | ------------ va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一個(gè)可變參數(shù)的地址
|——————————————— |
|——————————————————————————|
| |
|最后一個(gè)固定參數(shù) | ------------- start的起始地址
|—————————————— —| …
|—————————————————————————— |
| |
|——————————————— |-> 低內(nèi)存地址處



_INTSIZEOF 宏

宏的定義:

#define _INTSIZEOF(n)  ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

這個(gè)宏的作用是把sizeof(n) 向上取整作為 sizeof(int)的整數(shù)倍,用以在內(nèi)存中對(duì)齊。
看過實(shí)現(xiàn)之后我覺得這個(gè)宏寫的當(dāng)精彩。

對(duì)于兩個(gè)正整數(shù) x, n 總存在整數(shù) q, r 使得

x = nq + r, 其中 0<= r <n ?? ? ??? //最小非負(fù)剩余

q, r 是唯一確定的。q = [x/n], r = x - n[x/n]. 這個(gè)是帶余除法的一個(gè)簡(jiǎn)單形式。在 c 語言中, q, r 容易計(jì)算出來: q = x/n, r = x % n.


所謂把 x 按 n 對(duì)齊指的是:若 r=0, 取 qn, 若 r>0, 取 (q+1)n. 這也相當(dāng)于把 x 表示為:

x = nq + r’, 其中 -n < r’ <=0 ?????? ?? //最大非正剩余

nq 是我們所求。關(guān)鍵是如何用 c 語言計(jì)算它。由于我們能處理標(biāo)準(zhǔn)的帶余除法,所以可以把這個(gè)式子轉(zhuǎn)換成一個(gè)標(biāo)準(zhǔn)的帶余除法,然后加以處理:

x+n = qn + (n+r’),其中 0<n+r’<=n ??? ???? //最大正剩余

x+n-1 = qn + (n+r’-1), 其中 0<= n+r’-1 <n ?? ?? //最小非負(fù)剩余

所以 qn = [(x+n-1)/n]n. 用 c 語言計(jì)算就是:

((x+n-1)/n)*n

若 n 是 2 的方冪, 比如 2^m,則除為右移 m 位,乘為左移 m 位。所以把 x+n-1 的最低 m 個(gè)二進(jìn)制位清 0就可以了。得到:

(x+n-1) & (~(n-1))

先試著理解上面的,如果上面那些步驟沒看懂沒關(guān)系。我們這樣想,要把一個(gè)整數(shù)表達(dá)成另一個(gè)小的整數(shù)的整數(shù)倍,那么這個(gè)整數(shù)本身肯定只有兩種情況:1,他可以被這個(gè)小整數(shù)整除,也就是正好在小整數(shù)劃分區(qū)間的端點(diǎn)上。2,不可被整除,那就是落在某個(gè)區(qū)間上。我們要做的就是找到這個(gè)數(shù)的區(qū)間端點(diǎn)。
c語言printf源碼,C語言,c語言,開發(fā)語言,后端,開源
第一種情況: x正好是某個(gè)區(qū)間的端點(diǎn),那么x+n-1落在他本身的區(qū)間,c語言中的/是向下取整的,除以n剛好就是某個(gè)端點(diǎn)值。
第二種情況,x在某個(gè)區(qū)間中,x+n必然在他下一個(gè)區(qū)間上。那么x+n-1,又有兩種情況,一種是和x+n在同一個(gè)區(qū)間(x的下一個(gè)區(qū)間)上,第二種是在x下一個(gè)區(qū)間的左端點(diǎn)上,不可能和x在同一個(gè)區(qū)間。那么不管是哪一種情況x+n-1 進(jìn)行c語言的向下整除操作后都會(huì)落在x的右端點(diǎn)上,就達(dá)到了向上取整的目的。

最后解釋一下(x+n-1) & (~(n-1))的含義, 要執(zhí)行x+n-1 除以n的操作, 相當(dāng)于向右移動(dòng)。那么我們有這個(gè)結(jié)論 若 n 是 2 的方冪, 比如 2^m,則除為右移 m 位,乘為左移 m 位。所以把 x+n-1 的最低 m 個(gè)二進(jìn)制位清 0就可以了。 而n若為2^m的話,二進(jìn)制下就是1后接m個(gè)0,那么對(duì)n-1取反就是得到了m個(gè)0,在進(jìn)行&操作就行了。

回到va_start宏:

#define va_start(ap,v)( ap = (va_list)&v + _INTSIZEOF(v) )   // 得到第一個(gè)可變參數(shù)的地址

結(jié)合前面那張棧的圖,我們可以知道,最后一個(gè)固定參數(shù)的地址在第一個(gè)可變參數(shù)的地址下方,在給出固定參數(shù)的地址后,加上固定參數(shù)本身占用的內(nèi)存后,得到了第一個(gè)可變參數(shù)的起始地址。

注意:宏va_start是對(duì)參數(shù)的地址進(jìn)行操作的,要求參數(shù)地址必須是有效的。一些地址無效的類型不能當(dāng)作固定參數(shù)類型。比如:寄存器類型,它的地址不是有效的內(nèi)存地址值;數(shù)組和函數(shù)也不允許,他們的長(zhǎng)度是個(gè)問題。因此,這些類型時(shí)不能作為va函數(shù)的參數(shù)的。


va_arg

先看宏的定義:

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

這個(gè)宏主要做了兩件事情:
1、 指針ap指向下一個(gè)參數(shù)的地址
2、 強(qiáng)制類型轉(zhuǎn)換后得到用戶所指定的值

我們可以拆開來看這句話
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) /* 指針ap指向下一個(gè)參數(shù)的地址 */ 拆成:

1. ap += _INTSIZEOF(t);            // 當(dāng)前,ap已經(jīng)指向下一個(gè)參數(shù)了

2return *(t *)( ap - _INTSIZEOF(t))
/* ap減去當(dāng)前參數(shù)的大小得到當(dāng)前參數(shù)的地址,再強(qiáng)制類型轉(zhuǎn)換后返回它的值 */

用這個(gè)宏就進(jìn)行可變參數(shù)的遍歷操作,達(dá)到智能輸出的效果了。

va_end

   #define va_end(ap) ( ap = (va_list)0 ) 

這個(gè)宏很簡(jiǎn)單,就是將指針置空,而這個(gè)空間也不是在堆上的,也不用free了。

x86平臺(tái)定義為ap=(char*)0;使ap不再> 指向堆棧,而是跟NULL一樣.有些直接定義為((void*)0),這樣編譯器不會(huì)為va_end產(chǎn)生代碼,例如gcc在linux的x86平臺(tái)就是這樣定義的.



4 自己實(shí)現(xiàn)的可變參數(shù)函數(shù)

#include<stdio.h>
#include<stdarg.h>
int k = -1, t = 0;
int it;
char *ct = NULL;
char cc = '0';
double dd = 0;
int it2 = 0;
int i = 0;
char tt[30];

void f(int it) {
  while(it) {
    t = it % 10;
    tt[++k] = t + '0';
    it /= 10;
  }
}
void myprintf(char const* fmt, ...) {
  char const *p;
  va_list aq;
  va_start(aq, fmt);
  p = fmt;
  while(*p != '\0')  {
    if(*p != '%') {
      putchar(*p);
      p++;
      continue;
    }
    switch(*++p) {
      case 'd':
        it = va_arg(aq, int);
        if(it < 0) {
          putchar('-');
          it = -it;
        }
        f(it);
        for( ; k >= 0; k--) {
          putchar(tt[k]);
        }

        break;
      case 's':
        ct = va_arg(aq, char *);
        for( ; *ct; ct++) {
          putchar(*ct);
        }
        break;
      case 'c':
        cc = va_arg(aq, int);
        putchar(cc);
        break;
      case 'f':
        dd = va_arg(aq, double);
        if(dd < 0){
          putchar('-');
          dd = -dd; 
        } 
        
        it = (int)dd;
        it2 = it;
        dd = dd - it;
        dd *= 1000000;
        it = (int)dd;
        
        for(i = k+1;i <= k+6 ;i++ ) {
          tt[i] = '0';
        }   
        
        f(it);
        
        tt[k = i] = '.'; 
        if(it2 == 0){
          tt[++k]  = '0';
        }  
        
        f(it2);
        
        for( ; k >= 0; k--) {
          putchar(tt[k]);
        }
        
        break;
    }  
    p++;
  }
  va_end(aq);
}
int main() {
  
  myprintf("??%d, %s, >> %c, %d, %s , %f, %f", 10, "kkk", '&', 999, "aaaaa", 1234.15648777, 0.0012);
  
  //myprintf("??%f", 0.0012);

  return 0;
}



//   ??10, kkk, >> &, 999, aaaaa , 1234.156487, 0.001200  

算法還可以優(yōu)化,代碼也能更好看點(diǎn),但學(xué)習(xí)的目的已經(jīng)達(dá)到了



參考:文章來源地址http://www.zghlxwxcb.cn/news/detail-528407.html

  • https://blog.csdn.net/blueskybluesoul/article/details/121969786
  • https://www.cnblogs.com/saolv/p/7779364.html
  • https://blog.csdn.net/zhyjunFov/article/details/12017697
  • https://blog.csdn.net/weixin_45206746/article/details/117535332
  • http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=814501

到了這里,關(guān)于C語言printf函數(shù)實(shí)現(xiàn)解讀的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • C語言格式化輸出函數(shù)printf詳解——C語言基礎(chǔ)知識(shí)

    C語言格式化輸出函數(shù)printf詳解——C語言基礎(chǔ)知識(shí)

    (由于篇幅較長(zhǎng),內(nèi)容較全,建議收藏) printf函數(shù)的一般格式為: printf(格式控制字符串,輸出值參數(shù)表); 如: 其中, f=%f,c=%fn 是 格式控制字符串 , f,c 是 輸出值參數(shù)表 。 (1)格式控制字符串是用雙引號(hào)括起來的字符串,包括三類信息: 格式字符。格式字符由“%”

    2023年04月08日
    瀏覽(29)
  • 在 C 語言中添加注釋、包含頭文件和使用 printf 函數(shù)輸出字符串的方法

    注釋是程序員在編寫代碼時(shí)添加給自己或其他人的說明文字,用來提高代碼的可讀性,方便理解。注釋通常會(huì)添加在代碼的關(guān)鍵部分以及不易理解的地方。 在C語言中,注釋可以使用兩種方式:?jiǎn)涡凶⑨屢约岸嘈凶⑨尅涡凶⑨屖褂肻\\"http://\\\",多行注釋使用\\\"/* */\\\"。多行注釋不可嵌套

    2023年04月22日
    瀏覽(25)
  • C語言(輸入輸出函數(shù)getchar,putchar、gets、puts,scanf,printf的功能以及用法)

    C語言(輸入輸出函數(shù)getchar,putchar、gets、puts,scanf,printf的功能以及用法)

    int getchar( void ); 返回值為int,所以需要用一個(gè)int變量來接收,不管輸入幾個(gè)字符,每次都只接收第一個(gè)字符,常與while和putchar配合使用。 從下面這張圖可以看出,輸入一個(gè)空格也會(huì)打印 當(dāng)然,獲取一個(gè)字符用得不多,每次都需要獲取一串,所以我們可以配合while來使用。 用

    2024年02月02日
    瀏覽(24)
  • C語言基本語句(變量類型int、 float、 double、 char,函數(shù)scanf、printf、putchar()、getchar() )

    1. int, float, double, char ①整型int(對(duì)應(yīng)%d) ?int a,b; ?scanf(\\\"%d,%d\\\",a,b); printf (\\\"%d\\\",a); printf(\\\"我今天吃了%d個(gè)蘋果,在黑板上寫下整數(shù)%d,這很有趣。\\\",a,b); //printf(\\\"……\\\",變量名)中,“……”部分內(nèi)容比較自由,可隨便發(fā)揮,但必須包括%d,幾個(gè)變量名就對(duì)應(yīng)幾個(gè)%d ②單精度型浮點(diǎn)數(shù)

    2024年02月08日
    瀏覽(30)
  • 如何編寫一個(gè)可變參數(shù)函數(shù)?如何讓所有單片機(jī)的所有串口實(shí)現(xiàn)printf函數(shù)?

    (1)由于真的復(fù)習(xí)不下去,就想著寫一篇博客拉回自己的心思。于是想到了長(zhǎng)期有疑惑,但是一直沒有進(jìn)行深入了解的C語言可變參數(shù)函數(shù)。 (2)本人查閱了一些網(wǎng)上的資料,以及自己的理解寫出來了這一片博客。首先再次感謝肯哥的答疑。 (3)借鑒文章: C51單片機(jī)中如何

    2024年02月11日
    瀏覽(16)
  • APS開源源碼解讀: 排程工具 frepple

    https://github.com/frePPLe/frepple/tree/master 關(guān)鍵在于理解buffer的含義以及操作。 有consumer和producing, 數(shù)量有正負(fù) Identify Critical Paths: Start by identifying the critical paths in your scheduling process. Critical paths are sequences of tasks that have the least flexibility in terms of start times. These paths are essential to meetin

    2024年02月05日
    瀏覽(18)
  • 開源機(jī)器人SmallRobotArm機(jī)器人源碼解讀

    開源機(jī)器人SmallRobotArm機(jī)器人源碼解讀

    開源機(jī)器人SmallRobotArm是一個(gè)開源的6軸機(jī)械臂,都由步進(jìn)電機(jī)驅(qū)動(dòng),github地址:https://github.com/SkyentificGit/SmallRobotArm ?機(jī)器人長(zhǎng)這個(gè)樣子 2 歐拉角及姿態(tài)變換 由歐拉角求姿態(tài)矩陣 源碼中用的歐拉角是ZYZ順組的歐拉角。 已知世界坐標(biāo)的坐標(biāo)(x,y,z)和歐拉角(α,β,γ),求出對(duì)應(yīng)的姿

    2024年02月16日
    瀏覽(25)
  • KEIL/MDK中的標(biāo)準(zhǔn)C庫函數(shù)printf和malloc實(shí)現(xiàn)線程安全

    在ARM嵌入式開發(fā)中,編譯器提供的C庫函數(shù)有 一部分 不是線程安全的。 如果項(xiàng)目中運(yùn)行了第三方RTOS,在調(diào)用標(biāo)準(zhǔn)C庫函數(shù)時(shí)就要關(guān)心它們是不是線程安全的。 比如printf函數(shù),它是可重入的函數(shù),但是在多線程環(huán)境下打印的內(nèi)容可能會(huì)交叉亂序。 當(dāng)然這種問題還不算嚴(yán)重。 但

    2023年04月08日
    瀏覽(14)
  • Apache Doris 聚合函數(shù)源碼閱讀與解析|源碼解讀系列

    Apache Doris 聚合函數(shù)源碼閱讀與解析|源碼解讀系列

    筆者最近由于工作需要開始調(diào)研 Apache Doris,通過閱讀聚合函數(shù)代碼切入 Apache Doris 內(nèi)核,同時(shí)也秉承著開源的精神,開發(fā)了 array_agg 函數(shù)并貢獻(xiàn)給社區(qū)。筆者通過這篇文章記錄下對(duì)源碼的一些理解,同時(shí)也方便后面的新人更快速地上手源碼開發(fā)。 聚合函數(shù),顧名思義,即對(duì)一

    2024年01月25日
    瀏覽(15)
  • Flask 學(xué)習(xí)-88. jsonify() 函數(shù)源碼解讀深入學(xué)習(xí)

    flask 有個(gè)jsonify() 函數(shù),如果返回的是一個(gè)字典,那么調(diào)用 jsonify 創(chuàng)建一個(gè)響應(yīng)對(duì)象。 視圖函數(shù)的返回值會(huì)自動(dòng)轉(zhuǎn)換為一個(gè)響應(yīng)對(duì)象。 如果返回值是一個(gè)字符串,那么會(huì)被 轉(zhuǎn)換為一個(gè)包含作為響應(yīng)體的字符串、一個(gè) 200 OK 出錯(cuò)代碼 和一個(gè) text/html 類型的響應(yīng)對(duì)象。 如果返回值

    2024年02月03日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包