JPEG原理分析及JPEG解码器的调试

JPEG原理分析及其解码器的调试

一、实验目的

掌握JPEG编解码系统的基本原理。初步掌握复杂的数据压缩算法实现,并能根据理论分析需要实现所对应数据的输出。

二、实验内容

(一)实验原理

1、JPEG文件介绍及格式

(1)JPEG简介

JPEG( Joint Photographic Experts Group),联合图像专家组,是用于连续色调静态图像压缩的一种标准。主要采用的是预测编码(DPCM)、离散余弦变换(DCT)以及熵编码的联合编码方式。属于有损压缩格式,能够将图像压缩在很小的储存空间,一定程度上会造成图像数据的损伤。

(2)JPEG文件格式
  • SOI :Start of Image,图像开始;标记代码:2字节,固定值:0xFFD8

  • APP0:Application,应用程序,保留标记 0;标记代码 :2字节,固定值:0xFFE0
    包含9个具体字段:

    • ① 数据长度 2字节 ①~⑨9个字段的总长度
    • ② 标识符 5字节 固定值0x4A46494600,即字符串“JFIF0”
    • ③ 版本号 2字节 一般是0x0102,表示JFIF的版本号1.2
    • ④ X和Y的密度单位 1字节 只有三个值可选:
      0:无单位;1:点数/英寸;2:点数/厘米
    • ⑤ X方向像素密度 2字节 取值范围未知
    • ⑥ Y方向像素密度 2字节 取值范围未知
    • ⑦ 缩略图水平像素数目 1字节 取值范围未知
    • ⑧ 缩略图垂直像素数目 1字节 取值范围未知
    • ⑨ 缩略图RGB位图 长度可能是3的倍数 缩略图RGB位图数据
  • DQT:Define Quantization Table,定义量化表 ;标记代码:2字节,固定值:0xFFDB
    包含9个具体字段:

    • ① 数据长度 2字节 字段①和多个字段②的总长度
    • ② 量化表 数据长度-2字节
      • a) 精度及量化表ID 1字节
        高4位:精度,只有两个可选值 0:8位;1:16位
        低4位:量化表ID,取值范围为0~3
      • b) 表项 (64×(精度+1))字节
        例如8位精度的量化表,其表项长度为64×(0+1)=64字节
        本标记段中,字段②可以重复出现,表示多个量化表,但最多只能出现4次
  • SOF0:Start of Frame,帧图像开始;标记代码:2字节;固定值:0xFFC0
    包含9个具体字段:

    • ① 数据长度 2字节 ①~⑥六个字段的总长度
    • ② 精度 1字节 每个数据样本的位数 通常是8位,一般软件都不支持 12位和16位
    • ③ 图像高度 2字节 图像高度(单位:像素)
    • ④ 图像宽度 2字节 图像宽度(单位:像素)
    • ⑤ 颜色分量数 1字节 只有3个数值可选 1:灰度图;3:YCrCb或YIQ;4:CMYK 而JFIF中使用YCrCb,故这里颜色分量数恒为3
    • ⑥颜色分量信息 颜色分量数×3字节(通常为9字节)
      • a)颜色分量ID 1字节
      • b)水平/垂直采样因子 1字节
        高4位:水平采样因子
        低4位:垂直采样因子
      • c) 量化表 1字节 当前分量使用的量化表的ID
  • DHT:Define Huffman Table,定义哈夫曼表;标记代码:2字节;固定值:0xFFC4
    包含2个具体字段:

    • ① 数据长度 2字节
    • ② huffman表 数据长度-2字节
      表ID和表类型 1字节
      高4位:类型,只有两个值可选
      0:DC直流;1:AC交流 低4位:哈夫曼表ID,
      注意,DC表和AC表分开编码
      不同位数的码字数量 16字节
      编码内容:16个不同位数的码字数量之和(字节)本标记段中,字段②可以重复出现(一般4次),也可以只出现1次。
  • SOS:Start of Scan,扫描开始:12字节;标记代码:2字节;固定值:0xFFDA
    包含2个具体字段:

    • ①数据长度 2字节 ①~④两个字段的总长度
    • ②颜色分量数 1字节 应该和SOF中的字段⑤的值相同,即: 1:灰度图是;3: YCrCb或YIQ;4:CMYK。
    • ③颜色分量信息
      • a) 颜色分量ID 1字节
      • b) 直流/交流系数表号 1字节
        高4位:直流分量使用的哈夫曼树编号
        低4位:交流分量使用的哈夫曼树编号
    • ④ 压缩图像数据
      • a)谱选择开始 1字节 固定值0x00
      • b)谱选择结束 1字节 固定值0x3F
      • c)谱选择 1字节 在基本JPEG中总为00
  • EOI:End of Image,图像结束:2字节;标记代码:2字节,固定值:0xFFD9

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、JPEG编码过程

JPEG编码的过程如下图所示,解码是编码的逆过程。
在这里插入图片描述

(二)程序实现

1、读取文件

enum std_markers {
   
   DQT  = 0xDB, /* Define Quantization Table */
   SOF  = 0xC0, /* Start of Frame (size information) */
   DHT  = 0xC4, /* Huffman Table */
   SOI  = 0xD8, /* Start of Image */
   SOS  = 0xDA, /* Start of Scan */
   RST  = 0xD0, /* Reset Marker d0 -> .. */
   RST7 = 0xD7, /* Reset Marker .. -> d7 */
   EOI  = 0xD9, /* End of Image */
   DRI  = 0xDD, /* Define Restart Interval */
   APP0 = 0xE0,
};

解析 Segment Marker

static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
{
   
  unsigned int i, cid, table;
  unsigned int nr_components = stream[2];
#if TRACE
  fprintf(p_trace,"> SOS marker\n");
  fflush(p_trace);
#endif

#if SANITY_CHECK
  if (nr_components != 3)
    snprintf(error_string, sizeof(error_string),"We only support YCbCr image\n");
#endif

  stream += 3;
  for (i=0;i<nr_components;i++) {
   
     cid = *stream++;
     table = *stream++;
#if SANITY_CHECK
     if ((table&0xf)>=4)
	snprintf(error_string, sizeof(error_string),"We do not support more than 2 AC Huffman table\n");
     if ((table>>4)>=4)
	snprintf(error_string, sizeof(error_string),"We do not support more than 2 DC Huffman table\n");
     if (cid != priv->component_infos[i].cid)
        snprintf(error_string, sizeof(error_string),"SOS cid order (%d:%d) isn't compatible with the SOF marker (%d:%d)\n",
	      i, cid, i, priv->component_infos[i].cid);
#if TRACE
     fprintf(p_trace,"ComponentId:%d  tableAC:%d tableDC:%d\n", cid, table&0xf, table>>4);
	 fflush(p_trace);
#endif
#endif
     priv->component_infos[i].AC_table = &priv->HTAC[table&0xf];
     priv->component_infos[i].DC_table = &priv->HTDC[table>>4];
  }
  priv->stream = stream+3;
#if TRACE
  fprintf(p_trace,"< SOS marker\n");
  fflush(p_trace);
#endif
  return 0;
}

2、解析DQT

  • 得到量化表长度(可能包含多张量化表)
  • 得到量化表的精度
  • 得到及检查量化表的序号(只能是 0 —— 3)
  • 得到量化表内容(64 个数据)
static int parse_DQT(struct jdec_private *priv, const unsigned char *stream)
{
   
  int qi;
  float *table;
  const unsigned char *dqt_block_end;
  FILE* DQTfile;
#if TRACE
  fprintf(p_trace,"> DQT marker\n");
  fflush(p_trace);
#endif
  dqt_block_end = stream + be16_to_cpu(stream);
  stream += 2;	/* Skip length */

  while (stream < dqt_block_end)
   {
   
     qi = *stream++;
#if SANITY_CHECK
     if (qi>>4)
       snprintf(error_string, sizeof(error_string),"16 bits quantization table is not supported\n");
     if (qi>4)
       snprintf(error_string, sizeof(error_string),"No more 4 quantization table is supported (got %d)\n", qi);
#endif
     table = priv->Q_tables[qi];
     build_quantization_table(table, stream);
     stream += 64;
   }

#if TRACE
  fprintf(p_trace,"< DQT marker\n");
  fflush(p_trace);

  //************************************************
  DQTfile = fopen("DQTfile.txt", "a");
  fputs("量化表\n", DQTfile);
  for (int i = 0; i < 8; i++)
  {
   
      for (int j = 0; j < 8; j++)
      {
   
          fprintf(DQTfile, "%f ", *table);
          table++;
      }
      fputs("\n", DQTfile);
  }
  //************************************************

#endif
  return 0;
}

zig-zag 排序

static const unsigned char zigzag[64] = 
{
   
   0,  1,  5,  6, 14, 15, 27, 28,
   2,  4,  7, 13, 16, 26, 29, 42,
   3,  8, 12, 17, 25, 30, 41, 43,
   9, 11, 18, 24, 31, 40, 44, 53,
  10, 19, 23, 32, 39, 45, 52, 54,
  20, 22, 33, 38, 46, 51, 55, 60,
  21, 34, 37, 47, 50, 56, 59, 61,
  35, 36, 48, 49, 57, 58, 62, 63
};
static void build_quantization_table(float *qtable, const unsigned char *ref_table)
{
   
  /* Taken from libjpeg. Copyright Independent JPEG Group's LLM idct.
   * For float AA&N IDCT method, divisors are equal to quantization
   * coefficients scaled by scalefactor[row]*scalefactor[col], where
   *   scalefactor[0] = 1
   *   scalefactor[k] = cos(k*PI/16) * sqrt(2)    for k=1..7
   * We apply a further scale factor of 8.
   * What's actually stored is 1/divisor so that the inner loop can
   * use a multiplication rather than a division.
   */
  int i, j;
  static const double aanscalefactor[8] = {
   
     1.0, 1.387039845, 1.306562965, 1.175875602,
     1.0, 0.785694958, 0.541196100, 0.275899379
  };
  const unsigned char *zz = zigzag;

  for (i=0; i<8; i++) {
   
     for (j=0; j<8; j++) {
   
       *qtable++ = ref_table[*zz++] * aanscalefactor[i] * aanscalefactor[j];
     }
   }

}

3、解析SOF0

  • 得到每个 sample 的比特数、长宽、颜色分量数
  • 得到每个颜色分量的 ID、水平采样因子、垂直采样因子、使用的量化表
    序号(与 DQT 中序号对应)
static int parse_SOF(struct jdec_private *priv, const unsigned char *stream)
{
   
  int i, width, height, nr_components, cid, sampling_factor;
  int Q_table;
  struct component *c;
#if TRACE
  fprintf(p_trace,"> SOF marker\n");
  fflush(p_trace);
#endif
  print_SOF(stream);

  height = be16_to_cpu(stream+3);
  width  = be16_to_cpu(stream+5);
  nr_components = stream[7];
#if SANITY_CHECK
  if (stream[2] != 8)
    snprintf(error_string, sizeof(error_string),"Precision other than 8 is not supported\n");
  if (width>JPEG_MAX_WIDTH || height>JPEG_MAX_HEIGHT)
    snprintf(error_string, sizeof(error_string),"Width and Height (%dx%d) seems suspicious\n", width, height);
  if (nr_components != 3)
    snprintf(error_string, sizeof(error_string),"We only support YUV images\n");
  if (height%16)
    snprintf(error_string, sizeof(error_string),"Height need to be a multiple of 16 (current height is %d)\n", height);
  if (width%16)
    snprintf(error_string, sizeof(error_string),"Width need to be a multiple of 16 (current Width is %d)\n", width);
#endif
  stream += 8;
  for (i=0; i<nr_components; i++) {
   
     cid = *stream++;
     sampling_factor = *stream++;
     Q_table = *stream++;
     c = &priv->component_infos[i];
#if SANITY_CHECK
     c->cid = cid;
     if (Q_table >= COMPONENTS)
       snprintf(error_string, sizeof(error_string),"Bad Quantization table index (got %d, max allowed %d)\n", Q_table, COMPONENTS-1);
#endif
     c->Vfactor = sampling_factor&0xf;
     c->Hfactor = sampling_factor>>4;
     c->Q_table = priv->Q_tables[Q_table];
#if TRACE
     fprintf(p_trace,"Component:%d  factor:%dx%d  Quantization table:%d\n",
           cid, c->Hfactor, c->Hfactor, Q_table );
	 fflush(p_trace);
#endif

  }
  priv->width = width;
  priv->height = height;
#if TRACE
  fprintf(p_trace,"< SOF marker\n");
  fflush(p_trace);
#endif

  return 0;
}
static void print_SOF(const unsigned char *stream)
{
   
  int width, height, nr_components, precision;
#if TRACE
  const char *nr_components_to_string[] = {
   
     "????",
     "Grayscale",
     "????",
     "YCbCr",
     "CYMK"
  };
#endif

  precision = stream[2];
  height = be16_to_cpu(stream+3);
  width  = be16_to_cpu(stream+5);
  nr_components = stream[7];
#if TRACE
  fprintf(p_trace,"> SOF marker\n");
  fprintf(p_trace,"Size:%dx%d nr_components:%d (%s)  precision:%d\n", 
      width, height,
      nr_components, nr_components_to_string[nr_components],
      precision);
  fflush(p_trace);
#endif
}

4、解析DHT

  • 得到 Huffman 表的类型(AC、DC)、序号
  • 依据数据重建 Huffman 表
static int parse_DHT(struct jdec_private *priv, const unsigned char *stream)
{
   
  unsigned int count, i;
  unsigned char huff_bits[17];
  int length, index;
  FILE* HuffFile;
  HuffFile = fopen("huffmanFile.txt", "a"); 

  length = be16_to_cpu(stream) - 2;
  stream += 2;	/* Skip length */
#if TRACE
  fprintf(p_trace,"> DHT marker (length=%d)\n", length);
  fflush(p_trace);
#endif

  while (length>0) {
   
     index = *stream++;

     /* We need to calculate the number of bytes 'vals' will takes */
     huff_bits[0] = 0;
     count = 0;
     for (i=1; i<17; i++) {
   
	huff_bits[i] = *stream++;
	count += huff_bits[i];
     }
#if SANITY_CHECK
     if (count >= HUFFMAN_BITS_SIZE)
       snprintf(error_string, sizeof(error_string),"No more than %d bytes is allowed to describe a huffman table", HUFFMAN_BITS_SIZE);
     if ( (index &0xf) >= HUFFMAN_TABLES)
       snprintf(error_string, sizeof(error_string),"No more than %d Huffman tables is supported (got %d)\n", HUFFMAN_TABLES, index&0xf);

#if TRACE
     fprintf(p_trace,"Huffman table %s[%d] length=%d\n", (index&0xf0)? "AC" : "DC", index & 0xf, count);//index低位代表是几号表
     fflush(p_trace);

     //*****************************************************************************************************
     fprintf(HuffFile, "Huffman table %s[%d] length=%d\r\n", (index & 0xf0) ? "AC" : "DC", index & 0xf, count);
     fflush(HuffFile);
     //*****************************************************************************************************

#endif

#endif

     if (index & 0xf0 )
       build_huffman_table(huff_bits, stream, &priv->HTAC[index&0xf]);
     else
       build_huffman_table(huff_bits, stream, &priv->HTDC[index&0xf]);

     length -= 1;
     length -= 16;
     length -= count;
     stream += count;
  }
#if TRACE
  fprintf(p_trace,"< DHT marker\n");
  fflush(p_trace);
#endif
  return 0;
}

5、解析SOS

  • 得到解析每个颜色分量的 DC、AC 值所使用的 Huffman 表序号(与 DHT中序号对应)
static int parse_SOS(struct jdec_private *priv, const unsigned char *stream)
{
   
  unsigned int i, cid, table;
  unsigned int nr_components = stream[2];
#if TRACE
  fprintf(p_trace,"> SOS marker\n");
  fflush(p_trace);
#endif

#if SANITY_CHECK
  if (nr_components != 3)
    snprintf(error_string, sizeof(error_string),"We only support YCbCr image\n");
#endif

  stream += 3;
  for (i=0;i<nr_components;i++) {
   
     cid = *stream++;
     table = *stream++;
#if SANITY_CHECK
     if ((table&0xf)>=4)
	snprintf(error_string, sizeof(error_string),"We do not support more than 2 AC Huffman table\n");
     if ((table>>4)>=4)
	snprintf(error_string, sizeof(error_string),"We do not support more than 2 DC Huffman table\n");
     if (cid != priv->component_infos[i].cid)
        snprintf(error_string, sizeof(error_string),"SOS cid order (%d:%d) isn't compatible with the SOF marker (%d:%d)\n",
	      i, cid, i, priv->component_infos[i].cid);
#if TRACE
     fprintf(p_trace,"ComponentId:%d  tableAC:%d tableDC:%d\n", cid, table&0xf, table>>4);
	 fflush(p_trace);
#endif
#endif
     priv->component_infos[i].AC_table = &priv->HTAC[table&0xf];
     priv->component_infos[i].DC_table = &priv->HTDC[table>>4];
  }
  priv->stream = stream+3;
#if TRACE
  fprintf(p_trace,"< SOS marker\n");
  fflush(p_trace);
#endif
  return 0;
}

6、MCU

  • 依据每个分量的水平、垂直采样因子计算 MCU 的大小,并得到每个 MCU 中 8*8宏块的个数
  • 对每个宏块进行 Huffman 解码,得到 DCT 系数
  • 对每个宏块的 DCT 系数进行 IDCT,得到 Y、Cb、Cr
  • 遇到 Segment Marker RST 时,清空之前的 DC DCT 系数
  • 解完所有 MCU,解码结束
  • 将 Y、Cb、Cr 转化为需要的色彩空间并保存。
static void decode_MCU_1x1_3planes(struct jdec_private *priv)
{
   
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 8);
  
  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

/*
 * Decode a 1x1 directly in 1 color
 */
static void decode_MCU_1x1_1plane(struct jdec_private *priv)
{
   
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 8);
  
  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}


/*
 * Decode a 2x1
 *  .-------.
 *  | 1 | 2 |
 *  `-------'
 */
static void decode_MCU_2x1_3planes(struct jdec_private *priv)
{
   
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

/*
 * Decode a 2x1
 *  .-------.
 *  | 1 | 2 |
 *  `-------'
 */
static void decode_MCU_2x1_1plane(struct jdec_private *priv)
{
   
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);

  // Cr
  process_Huffman_data_unit(priv, cCr);
}


/*
 * Decode a 2x2
 *  .-------.
 *  | 1 | 2 |
 *  |---+---|
 *  | 3 | 4 |
 *  `-------'
 */
static void decode_MCU_2x2_3planes(struct jdec_private *priv)
{
   
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

/*
 * Decode a 2x2 directly in GREY format (8bits)
 *  .-------.
 *  | 1 | 2 |
 *  |---+---|
 *  | 3 | 4 |
 *  `-------'
 */
static void decode_MCU_2x2_1plane(struct jdec_private *priv)
{
   
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+8, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2, 16);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64*2+8, 16);

  // Cb
  process_Huffman_data_unit(priv, cCb);

  // Cr
  process_Huffman_data_unit(priv, cCr);
}

/*
 * Decode a 1x2 mcu
 *  .---.
 *  | 1 |
 *  |---|
 *  | 2 |
 *  `---'
 */
static void decode_MCU_1x2_3planes(struct jdec_private *priv)
{
   
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 8);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64, 8);

  // Cb
  process_Huffman_data_unit(priv, cCb);
  IDCT(&priv->component_infos[cCb], priv->Cb, 8);

  // Cr
  process_Huffman_data_unit(priv, cCr);
  IDCT(&priv->component_infos[cCr], priv->Cr, 8);
}

/*
 * Decode a 1x2 mcu
 *  .---.
 *  | 1 |
 *  |---|
 *  | 2 |
 *  `---'
 */
static void decode_MCU_1x2_1plane(struct jdec_private *priv)
{
   
  // Y
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y, 8);
  process_Huffman_data_unit(priv, cY);
  IDCT(&priv->component_infos[cY], priv->Y+64, 8);

  // Cb
  process_Huffman_data_unit(priv, cCb);

  // Cr
  process_Huffman_data_unit(priv, cCr);
}

7、tinyjpeg.c完整程序

/*
 * Small jpeg decoder library
 *
 * Copyright (c) 2006, Luc Saillard <[email protected]>
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * - Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 *
 * - Neither the name of the author nor the names of its contributors may be
 *  used to endorse or promote products derived from this software without
 *  specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值