精华内容
下载资源
问答
  • 类文件解析004-解析常量元素

    万次阅读 2019-05-29 14:53:08
    我们在上篇文章介绍了创建constantPoolOop的过程,本文我们就来看看解析常量元素的过程.这里涉及的方法为:ClassFileParser::parse_constant_pool_entries。代码如下: void ClassFileParser::parse_constant_pool_...

    我们在上篇文章介绍了创建constantPoolOop的过程,本文我们就来看看解析常量池元素的过程.这里涉及的方法为:ClassFileParser::parse_constant_pool_entries。代码如下:

    void ClassFileParser::parse_constant_pool_entries(constantPoolHandle cp, int length, TRAPS) {
      // Use a local copy of ClassFileStream. It helps the C++ compiler to optimize
      // this function (_current can be allocated in a register, with scalar
      // replacement of aggregates). The _current pointer is copied back to
      // stream() when this function returns. DON'T call another method within
      // this method that uses stream().
      // 1.使用classfilestream的本地副本。它有助于C++编译器优化这个函数(可以在寄存器中分配_current,用标量替换集合)。当函数返回时,当前指针将被复制回stream()。不要在此方法中调用使用stream()的其他方法。
      ClassFileStream* cfs0 = stream();
      ClassFileStream cfs1 = *cfs0;
      ClassFileStream* cfs = &cfs1;
    #ifdef ASSERT
      assert(cfs->allocated_on_stack(),"should be local");
      u1* old_current = cfs0->current();
    #endif
    
      // Used for batching symbol allocations.
      // 2. 进行符号批量的分配
      const char* names[SymbolTable::symbol_alloc_batch_size]; //SymbolTable::symbol_alloc_batch_size = 8
      int lengths[SymbolTable::symbol_alloc_batch_size];
      int indices[SymbolTable::symbol_alloc_batch_size];
      unsigned int hashValues[SymbolTable::symbol_alloc_batch_size];
      int names_count = 0;
    
      // 3.parsing  Index 0 is unused 解析, 下标0是没使用的
      for (int index = 1; index < length; index++) {
        // Each of the following case guarantees one more byte in the stream
        // for the following tag or the access_flags following constant pool,
        // so we don't need bounds-check for reading tag.
    	// 以下每种情况都保证流中的以下标记或常量池后面的访问标记有一个以上的字节,因此我们不需要对读取标记进行边界检查
    	// 3.1  读取标记
        u1 tag = cfs->get_u1_fast();
        switch (tag) {
          case JVM_CONSTANT_Class :
            {
              cfs->guarantee_more(3, CHECK);  // name_index, tag/access_flags
              u2 name_index = cfs->get_u2_fast();
              cp->klass_index_at_put(index, name_index);
            }
            break;
          case JVM_CONSTANT_Fieldref :
            {
              cfs->guarantee_more(5, CHECK);  // class_index, name_and_type_index, tag/access_flags
              u2 class_index = cfs->get_u2_fast();
              u2 name_and_type_index = cfs->get_u2_fast();
              cp->field_at_put(index, class_index, name_and_type_index);
            }
            break;
            .... 省略
          default:
            classfile_parse_error(
              "Unknown constant tag %u in class file %s", tag, CHECK);
            break;
        }
      }
    
      // Allocate the remaining symbols 分配剩余的符号
      if (names_count > 0) {
        oopFactory::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
      }
    
      // Copy _current pointer of local copy back to stream().
    #ifdef ASSERT
      assert(cfs0->current() == old_current, "non-exclusive use of stream()");
    #endif
      cfs0->set_current(cfs1.current());
    }
    
    

    可以看到,该方法共有5个步骤:

    1. 使用classfilestream的本地副本。它有助于C++编译器优化这个函数(可以在寄存器中分配_current,用标量替换集合)。当函数返回时,当前指针将被复制回stream()。不要在此方法中调用使用stream()的其他方法。
    2. 进行符号批量的分配
    3. 依次解析常量池元素
    4. 分配剩余的符号
    5. 设置classfilestream的内部指针

    这里需要介绍一下符号的批量分配,这里涉及的常量池的元素为CONSTANT_Utf8_info.由于在处理该元素的时候,会在符号表中新建符号,为了提高效率,因此在此处使用批处理的方式,分批处理.


    常量池元素解析

    在解析时,是通过循环处理的,其下标是从1开始的.其原因如下:

    constant_pool表的索引值只有在大于0且小于constant_pool_count时才会被认为是有效的,对于long和double类型有例外情况。虽然值为0的constant_pool索引是无效的,但其他用到常量池的数据结构可以使用索引0来表示“不引用任何一个常量池项”的意思

    解析JVM_CONSTANT_Class

    CONSTANT_Class_info结构用于表示类或接口,格式如下:

    CONSTANT_Class_info {  
    u1 tag;  
    u2 name_index; 
    } 
    

    CONSTANT_Class_info结构的项的说明:

    • tag CONSTANT_Class_info结构的tag项的值为CONSTANT_Class
    • name_index name_index项的值,必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,代表一个有效的类或接口二进制名称的内部形式。

    因为数组也是由对象表示,所以字节码指令anewarray和multianewarray可以通过常量池中的CONSTANT_Class_info(§4.4.1)结构来引用类数组。对于这些数组,类的名字就是数组类型的描述符,例如:

    • 表示int二维数组类型 int[][] 的名字是: [[I
    • 表示一维Thread数组类型 Thread[]的名字是: [Ljava/lang/Thread;

    一个有效的数组类型描述符中描述的数组维度必须小于等于255。

    这里涉及的代码为:

     u1 tag = cfs->get_u1_fast();
        
    case JVM_CONSTANT_Class :
    {
      cfs->guarantee_more(3, CHECK);  // name_index, tag/access_flags
      u2 name_index = cfs->get_u2_fast();
      cp->klass_index_at_put(index, name_index);
    }
    break;
    

    在case JVM_CONSTANT_Class: 中,调用了cfs->guarantee_more(3, CHECK) 方法保证在读取了tag类型后, classfilestream 还有3个字节可读. 这3个字节的内容可能为:

    • name_index + 下一个常量池元素的tag
    • name_index + access_flags 的第1个字节.此情况只在该元素是最后一个常量池元素时发生

    读取到name_index后,调用cp->klass_index_at_put(index, name_index) 进行赋值.其代码如下:

    // 在hotspot/src/share/vm/oops/constantPoolOop.hpp
    void klass_index_at_put(int which, int name_index) {
    tag_at_put(which, JVM_CONSTANT_ClassIndex);
    *int_at_addr(which) = name_index;
    }
    

    首先看第1步:设置tag数组的值,代码如下:

    // 在hotspot/src/share/vm/oops/constantPoolOop.hpp
    void tag_at_put(int which, jbyte t)          { tags()->byte_at_put(which, t); }
    
    //hotspot/src/share/vm/oops/typeArrayOop.hpp
    void byte_at_put(int which, jbyte contents)     { *byte_at_addr(which) = contents; }
      
    

    其最终会在constantPoolOop 所持有的tags()中下标为index的元素赋值为101(JVM_CONSTANT_ClassIndex的值为101).当然,这个值是临时的

    接着看第2步: *int_at_addr(which) = name_index;其代码如下:

     jint* int_at_addr(int which) const {
        assert(is_within_bounds(which), "index out of bounds");
        return (jint*) &base()[which];
      }
      
     intptr_t* base() const { return (intptr_t*) (((char*) this) + sizeof(constantPoolOopDesc)); }
    

    其最终会在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存name_index.


    解析JVM_CONSTANT_Fieldref JVM_CONSTANT_Methodref JVM_CONSTANT_InterfaceMethodref

    字段,方法和接口方法有类似的结构表示:

    字段:

    CONSTANT_Fieldref_info {      
    u1 tag;      
    u2 class_index;
    u2 name_and_type_index; 
    }
    

    方法:

    CONSTANT_Methodref_info {    
    u1 tag;         
    u2 class_index;       
    u2 name_and_type_index; 
    } 
    

    接口方法:

    CONSTANT_InterfaceMethodref_info {    
    u1 tag;    
    u2 class_index;    
    u2 name_and_type_index; 
    } 
    

    这些结构各项的说明如下:

    • tag

      CONSTANT_Fieldref_info结构的tag项的值为CONSTANT_Fieldref(9)。 CONSTANT_Methodref_info结构的tag项的值为CONSTANT_Methodref(10)。 CONSTANT_InterfaceMethodref_info结构的tag项的值为 CONSTANT_InterfaceMethodref(11)。

    • class_index

      class_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。 CONSTANT_Methodref_info结构的class_index项的类型必须是类(不能是接口)。CONSTANT_InterfaceMethodref_info结构的class_index项的类型必须是接口(不能是类)。CONSTANT_Fieldref_info结构的class_index项的类型既可以是类也可以是接口

    • name_and_type_index

      name_and_type_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,它表示当前字段或方法的名字和描述符。

      在一个CONSTANT_Fieldref_info结构中,给定的描述符必须是字段描述符(§4.3.2)。而CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info中给定的描述符必须是方法描述符(§4.3.3)。

      如果一个CONSTANT_Methodref_info结构的方法名以“<”(’\u003c’)开头,则说明这个方法名是特殊的<init>,即这个方法是实例初始化方法(§2.9),它的返回类型必须为空。

    这里涉及的代码为:

    u1 tag = cfs->get_u1_fast();
    case JVM_CONSTANT_Fieldref :
    {
      cfs->guarantee_more(5, CHECK);  // class_index, name_and_type_index, tag/access_flags
      u2 class_index = cfs->get_u2_fast();
      u2 name_and_type_index = cfs->get_u2_fast();
      cp->field_at_put(index, class_index, name_and_type_index);
    }
    break;
    case JVM_CONSTANT_Methodref :
    {
      cfs->guarantee_more(5, CHECK);  // class_index, name_and_type_index, tag/access_flags
      u2 class_index = cfs->get_u2_fast();
      u2 name_and_type_index = cfs->get_u2_fast();
      cp->method_at_put(index, class_index, name_and_type_index);
    }
    break;
    case JVM_CONSTANT_InterfaceMethodref :
    {
      cfs->guarantee_more(5, CHECK);  // class_index, name_and_type_index, tag/access_flags
      u2 class_index = cfs->get_u2_fast();
      u2 name_and_type_index = cfs->get_u2_fast();
      cp->interface_method_at_put(index, class_index, name_and_type_index);
    }
    break;
    

    在这三种情况中中,调用了cfs->guarantee_more(5, CHECK) 方法保证在读取了tag类型后, classfilestream 还有5个字节可读. 这5个字节的内容可能为:

    • class_index + name_and_type_index + 下一个常量池元素的tag
    • class_index + name_and_type_index + access_flags 的第1个字节.此情况只在该元素是最后一个常量池元素时发生

    在处理JVM_CONSTANT_Fieldref时,会调用 cp->field_at_put(index, class_index, name_and_type_index)进行赋值,其代码如下:

    void field_at_put(int which, int class_index, int name_and_type_index) {
    tag_at_put(which, JVM_CONSTANT_Fieldref);
    *int_at_addr(which) = ((jint) name_and_type_index<<16) | class_index;
    }
    

    可见:其最终会constantPoolOop 所持有的tags()中下标为index的元素赋值为9(JVM_CONSTANT_Fieldref的值为9).在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存name_index和name_and_type_index(经过压缩).

    对于JVM_CONSTANT_Methodref, JVM_CONSTANT_InterfaceMethodref 和 JVM_CONSTANT_Fieldref 类似,这里就不再展开了.


    解析JVM_CONSTANT_Integer

    CONSTANT_Intrger_info 表示Int的数值类型.其结构如下:

    CONSTANT\_Integer\_info {  
    u1 tag;  
    u4 bytes; 
    } 
    
    • tag

      CONSTANT_Integer_info结构的tag项的值是CONSTANT_Integer(3)。

    • bytes

      CONSTANT_Intrger_info结构的bytes项表示int常量的值,按照Big-Endian的顺序存储。

    涉及的代码如下:

    u1 tag = cfs->get_u1_fast();
    case JVM_CONSTANT_Integer :
    {
      cfs->guarantee_more(5, CHECK);  // bytes, tag/access_flags
      u4 bytes = cfs->get_u4_fast();
      cp->int_at_put(index, (jint) bytes);
    }
    break;
    

    这里首先通过 get_u4_fast()获取 int常量的值.注意,这里进行了字节的转换.具体细节在类加载流程-002 中有叙述,这里就不在展开了.

    最后调用cp->int_at_put(index, (jint) bytes) 进行处理.代码如下:

    void int_at_put(int which, jint i) {
    tag_at_put(which, JVM_CONSTANT_Integer);
    *int_at_addr(which) = i;
    }
    

    可见,其最终是在constantPoolOop 所持有的tags()中下标为index的元素赋值为3(JVM_CONSTANT_Integer的值为3).在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存Int值.

    对于JVM_CONSTANT_Float,JVM_CONSTANT_Long,JVM_CONSTANT_Double 来说,其处理过程和CONSTANT_Intrger_info类似,区别在于,JVM_CONSTANT_Long,JVM_CONSTANT_Double 其对应的值为8个字节,因此在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存值的时候,需要占用8个字节.代码如下:

    cp->long_at_put(index, bytes);
    
    void long_at_put(int which, jlong l) {
    tag_at_put(which, JVM_CONSTANT_Long);
    // *long_at_addr(which) = l;
    Bytes::put_native_u8((address)long_at_addr(which), *((u8*) &l));
    }
    

    解析JVM_CONSTANT_Utf8

    CONSTANT_Utf8_info结构用于表示字符串常量的值:

    CONSTANT\_Utf8\_info {  
    u1 tag;  
    u2 length;  
    u1 bytes[length]; 
    } 
    

    CONSTANT_Utf8_info结构各项的说明如下:

    • tag

      CONSTANT_Utf8_info结构的tag项的值为CONSTANT_Utf8(1)。

    • length

      length项的值指明了bytes[]数组的长度(注意,不能等同于当前结构所表示的String对象的长度),CONSTANT_Utf8_info结构中的内容是以length属性确定长度而不是以null作为字符串的终结符。

    • bytes[]

      bytes[]是表示字符串值的byte数组,bytes[]数组中每个成员的byte值都不会是0,也不在0xf0至0xff范围内。

    这里涉及的代码为:

    u1 tag = cfs->get_u1_fast();
    case JVM_CONSTANT_Utf8 :
    {
      cfs->guarantee_more(2, CHECK);  // utf8_length
      // 1. 读取字符串长度
      u2  utf8_length = cfs->get_u2_fast();
      u1* utf8_buffer = cfs->get_u1_buffer();
      assert(utf8_buffer != NULL, "null utf8 buffer");
      // Got utf8 string, guarantee utf8_length+1 bytes, set stream position forward.
      cfs->guarantee_more(utf8_length+1, CHECK);  // utf8 string, tag/access_flags
      cfs->skip_u1_fast(utf8_length);
    
      // Before storing the symbol, make sure it's legal
      // 2. 如果需要进行验证的话,则验证其格式是否正确
      if (_need_verify) {
        verify_legal_utf8((unsigned char*)utf8_buffer, utf8_length, CHECK);
      }
    
      if (AnonymousClasses && has_cp_patch_at(index)) {
        Handle patch = clear_cp_patch_at(index);
        guarantee_property(java_lang_String::is_instance(patch()),
                           "Illegal utf8 patch at %d in class file %s",
                           index, CHECK);
        char* str = java_lang_String::as_utf8_string(patch());
        // (could use java_lang_String::as_symbol instead, but might as well batch them)
        utf8_buffer = (u1*) str;
        utf8_length = (int) strlen(str);
      }
    
      unsigned int hash;
      // 3. 从符号表中查找该字符串是否存在
      symbolOop result = SymbolTable::lookup_only((char*)utf8_buffer, utf8_length, hash);
      if (result == NULL) {// 4.1 如果符号表中没有,添加到容器中
        names[names_count] = (char*)utf8_buffer;
        lengths[names_count] = utf8_length;
        indices[names_count] = index;
        hashValues[names_count++] = hash;
        // 4.2 如果容器的size 达到阈值, 则真正的进行批处理,向符号表进行添加
        if (names_count == SymbolTable::symbol_alloc_batch_size) {
          oopFactory::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
          names_count = 0;
        }
      } else {
        // 5. 如果该符号存在的话,则直接复用
        cp->symbol_at_put(index, result);
      }
    }
    break;
    
    
    // 6. 如果解析完常量池元素后,仍有剩余,则再次进行批处理,向符号表进行添加
    if (names_count > 0) {
    oopFactory::new_symbols(cp, names_count, names, lengths, indices, hashValues, CHECK);
    }
    

    此处我们重点来看一下 oopFactory::new_symbols方法.其代码如下:

    // 位于hotspot/src/share/vm/memory/oopFactory.hpp
    static void new_symbols(constantPoolHandle cp, int names_count,
                          const char** name, int* lengths,
                          int* cp_indices, unsigned int* hashValues,
                          TRAPS) {
    SymbolTable::add(cp, names_count, name, lengths, cp_indices,
                     hashValues, CHECK);
    }
    

    调用:

    void SymbolTable::add(constantPoolHandle cp, int names_count,
                          const char** names, int* lengths, int* cp_indices,
                          unsigned int* hashValues, TRAPS) {
    
      symbolKlass* sk  = (symbolKlass*) Universe::symbolKlassObj()->klass_part();
      symbolOop sym_oops[symbol_alloc_batch_size];
      // 1. 尝试进行分配,在UseConcMarkSweepGC和UseParallelGC 不会进行分配的
      bool allocated = sk->allocate_symbols(names_count, names, lengths,
                                            sym_oops, CHECK);
      if (!allocated) {
        // do it the hard way 2. 如果分配失败的话,则换种方式进行分配
        for (int i=0; i<names_count; i++) {
          assert(!Universe::heap()->is_in_reserved(names[i]) || GC_locker::is_active(),
             "proposed name of symbol must be stable");
    
          // We assume that lookup() has been called already, that it failed,
          // and symbol was not found.  We create the symbol here.
          symbolKlass* sk  = (symbolKlass*) Universe::symbolKlassObj()->klass_part();
          symbolOop s_oop = sk->allocate_symbol((u1*)names[i], lengths[i], CHECK);
          symbolHandle sym (THREAD, s_oop);
    
          // Allocation must be done before grabbing the SymbolTable_lock lock
          MutexLocker ml(SymbolTable_lock, THREAD);
    
          SymbolTable* table = the_table();
          int index = table->hash_to_index(hashValues[i]);
          symbolOop s = table->basic_add(sym, index, (u1*)names[i], lengths[i],
                                           hashValues[i], CHECK);
          cp->symbol_at_put(cp_indices[i], s);
        }
        return;
      }
    
      // 3. 如果在第一步成功的话.则此时需要将symbol添加到符号表中
      symbolHandle syms[symbol_alloc_batch_size];
      for (int i=0; i<names_count; i++) {
        syms[i] = symbolHandle(THREAD, sym_oops[i]);
      }
    
      // Allocation must be done before grabbing the SymbolTable_lock lock
      MutexLocker ml(SymbolTable_lock, THREAD);
    
      SymbolTable* table = the_table();
      bool added = table->basic_add(syms, cp, names_count, names, lengths,
                                    cp_indices, hashValues, CHECK);
      assert(added, "should always return true");
    }
    

    其步骤如下:

    1. 尝试进行分配,在UseConcMarkSweepGC和UseParallelGC 不会进行分配的.这里的分配是指首先在堆中分配symbolOop.将结果保存在之前声明的sym_oops中.

    2. 如果分配失败的话,则换种方式进行分配,直接插入到符号表中

    3. 如果在第1步成功的话.则此时需要将symbol添加到符号表中

    接下来,我们只介绍第1步情况,第2步和第1步流程类似.

    其涉及的代码如下:

    bool symbolKlass::allocate_symbols(int names_count, const char** names,
                                       int* lengths, symbolOop* sym_oops, TRAPS) {
      // 1. 如果是cms 或 ParallelGC 则直接返回false
      if (UseConcMarkSweepGC || UseParallelGC) {
        
        return false;
      }
    
      assert(names_count > 0, "can't allocate 0 symbols");
    
      // 2. 计算每个符号需要分配的大小,并计算需要统一分配的大小
      int total_size = 0;
      int i, sizes[SymbolTable::symbol_alloc_batch_size];
      for (i=0; i<names_count; i++) {
        int len = lengths[i];
        // 2.1 如果其符号的长度大于 (1 << 16) -1 则直接返回false
        if (len > symbolOopDesc::max_length()) {
          return false;
        }
        int sz = symbolOopDesc::object_size(len);
        sizes[i] = sz * HeapWordSize;
        total_size += sz;
      }
      // 3. 在堆上进行分配
      symbolKlassHandle h_k(THREAD, as_klassOop());
      HeapWord* base = Universe::heap()->permanent_mem_allocate(total_size);
      if (base == NULL) {
        return false;
      }
    
      // CAN'T take any safepoint during the initialization of the symbol oops !
      // 在初始化symbol oop 中的过程中不能得到任何的安全点
      No_Safepoint_Verifier nosafepoint;
    
      // 4. 实例化数据,并保存到容器中
      klassOop sk = h_k();
      int pos = 0;
      for (i=0; i<names_count; i++) {
        symbolOop s = (symbolOop) (((char*)base) + pos);
        s->set_mark(markOopDesc::prototype());
        s->set_klass(sk);
        s->set_utf8_length(lengths[i]);
        const char* name = names[i];
        for (int j=0; j<lengths[i]; j++) {
          s->byte_at_put(j, name[j]);
        }
    
        assert(s->is_parsable(), "should be parsable here.");
    
        sym_oops[i] = s;
        pos += sizes[i];
      }
      return true;
    }
    
    

    这里的代码比较简单,接着我们看第三步即可.其最终调用SymbolTable::basic_add.代码如下:

    bool SymbolTable::basic_add(symbolHandle* syms,
                                constantPoolHandle cp, int names_count,
                                const char** names, int* lengths,
                                int* cp_indices, unsigned int* hashValues,
                                TRAPS) {
      // Cannot hit a safepoint in this function because the "this" pointer can move.
      No_Safepoint_Verifier nsv;
    
      for (int i=0; i<names_count; i++) {
        assert(syms[i]->equals(names[i], lengths[i]), "symbol must be properly initialized");
        // 1. 获得符号对应的hash    
        unsigned int hashValue;
        if (use_alternate_hashcode()) {
          hashValue = hash_symbol(names[i], lengths[i]);
        } else {
          hashValue = hashValues[i];
        }
        // 2. 根据hash找到对应的index,并进行查找
        int index = hash_to_index(hashValue);
        symbolOop test = lookup(index, names[i], lengths[i], hashValue);
        if (test != NULL) {
          // 2.1 如果找到的话,则直接复用即可
          cp->symbol_at_put(cp_indices[i], test);
        } else {
          // 2.2 如果没有找到的话,则进行添加,然后添加到constantPool 中
          symbolOop sym = syms[i]();
          HashtableEntry* entry = new_entry(hashValue, sym);
          add_entry(index, entry);
          cp->symbol_at_put(cp_indices[i], sym);
        }
      }
      return true;  // always returns true
    }
    

    这里关于符号表的细节,在类加载流程004 中介绍.

    接下来让我们看一下cp->symbol_at_put(cp_indices[i], sym).代码如下:

    void symbol_at_put(int which, symbolOop s) {
    tag_at_put(which, JVM_CONSTANT_Utf8);
    oop_store_without_check(obj_at_addr(which), oop(s));
    }
    

    可见: 其最终是在constantPoolOop 所持有的tags()中下标为index的元素赋值为1(JVM_CONSTANT_Utf8的值为1).在constantPoolOop内存中分配的那length个指针长度中下标为index的地方保存symbolOop.


    总结

    关于常量池其他元素,由于比较简单,就不再展开.下面用一个表格来总结一下:

    常量池类型 constantPoolOop 中保存的值 tags 中保存的值
    JVM_CONSTANT_Class 保存name_index 101(JVM_CONSTANT_ClassIndex的值为101) 该值为临时
    JVM_CONSTANT_Fieldref ((jint) name_and_type_index<<16) | class_index 9(JVM_CONSTANT_Fieldref的值为9)
    JVM_CONSTANT_Methodref ((jint) name_and_type_index<<16) | class_index 10(JVM_CONSTANT_Methodref的值为10)
    JVM_CONSTANT_InterfaceMethodref ((jint) name_and_type_index<<16) | class_index 11(JVM_CONSTANT_InterfaceMethodref的值为11)
    JVM_CONSTANT_String string_index 103(JVM_CONSTANT_StringIndex的值为103) 此值为临时
    JVM_CONSTANT_MethodHandle ((jint) ref_index<<16) | ref_kind 15(JVM_CONSTANT_MethodHandle的值为15)
    JVM_CONSTANT_MethodType ref_index 16(JVM_CONSTANT_MethodType的值为16)
    JVM_CONSTANT_InvokeDynamicTrans ((jint) name_and_type_index<<16) | bootstrap_method_index 17(JVM_CONSTANT_InvokeDynamicTrans的值为17)
    JVM_CONSTANT_InvokeDynamic ((jint) name_and_type_index<<16) | bootstrap_specifier_index 18(JVM_CONSTANT_InvokeDynamic的值为18)
    JVM_CONSTANT_Integer int 值 3(JVM_CONSTANT_Integer的值为3)
    JVM_CONSTANT_Float float 值 4(JVM_CONSTANT_Float的值为4)
    JVM_CONSTANT_Long long 值 占64位 5(JVM_CONSTANT_Long的值为5)
    JVM_CONSTANT_Double ddouble 值 占64位 6(JVM_CONSTANT_Double的值为6)
    JVM_CONSTANT_NameAndType ((jint) signature_index<<16) | name_index 12(JVM_CONSTANT_NameAndType的值为12)
    JVM_CONSTANT_Utf8 symbolOop 1(JVM_CONSTANT_Utf8的值为1)
    展开全文
  • 在程序执行过程中,其值不发生改变的量称为常量 在程序中,常量可以不以说明而直接使用 常量分类 直接常量(字面常量/字面值) 实型常量:4.6F、-1.23 字符常量:’a‘、’b’ 字符串常量:”Hello world’ 符号...

    学习目标

    • C语言中的基本元素
    • 常量、变量的概念和命名规则
    • 变量的声明、初始化和定义

    C语言中的基本元素
    变量
    运算符
    指令
    程序

    常量
    在程序执行过程中,其值不发生改变的量称为常量 在程序中,常量可以不以说明而直接使用 常量分类

    • 直接常量(字面常量/字面值)
    • 实型常量:4.6F、-1.23
    • 字符常量:’a‘、’b’
    • 字符串常量:”Hello world’

    符号常量

    • 符号常量定义成宏的形式:#define 符号常量 值

    • 习惯上符号常量使用大写字母表示

    常量示例const_var.c

    /*
     *文件名:const_var.c
     *描述:常量、变量的介绍和使用
     * */
    #include <stdio.h>
    #define PRICE 30        //符号常量
    
    
    int main(void)
    {
            int num = 10;
            int total = 0;  //字面常量
            total = num*PRICE;
            printf("total= %d\n",total);
            return 0;
    }  

    符号常量的特点:
    符号常量与变量不同,它的值在其作用域内不能改变,也不能再被赋值。

    • 含义清楚
    • 能做到”一改全改“

    符号常量不允许赋值,PRICE = 40,否则会报错: 错误:赋值运算的左操作数必须是左值

    变量
    在程序执行过程中,其值可以改变的量称为变量
    一个变量应该有一个名字,在内存中占据一定的存储单元。
    变量定义必须在变量使用之前,一般放在函数体的开关部分。
    要分区变量名和变量值是两个不同的概念

    标识符的概念
    用来标识变量名、符号常量名、函数名、数组名、类型名、文件名的有效字符序列称为标识符。

    标识符的命名规则

    • 可以由字母、数字和_(下划线)组合而成。
    • 不能包含除_以外的任何特殊字符,如:%、#、逗号、空格等。
    • 必须以字母或_开头
    • 不能包含空白字符(换行符、空格和制表符称为空白符)
    • C语言中的某些关键字(如:int、float等)称为保留字,具有特殊意义不能用途标识符
    • 区分大小写

    变量的声明 ,定义和初始化
    变量的声明
    主要是告诉编译器变量的类型名字,在使用之前知道有这样一个变量的存在,这并不分配存储空间。
    格式:数据类型 变量名
    如:int count;
    double ratio;
    变量的定义和初始化
    为变量分配存储空间,同时指明变量的类型和名字
    格式:数据类型 变量名= 变量值
    如:int count = 100; //在定义时进行初始化

    是否分配存储空间
    int a;//定义性声明,
    extern int a;//引用性声明

    在C语言中,变量有且只能定义一次,但声明可以有多次。


    本节课的基本元素
    C语言中的基本元素
    常量、变量的概念和命名规则
    变量的声明,定义,初始化

    下节课,数据类型

    展开全文
  • C语言的基本元素-常量

    千次阅读 2011-04-20 19:45:00
    按照所属数据类型分四种常量: 整形常量 实行常量 字符串常量 字符型常量 一、整型常量 •三种形式: n十进制整数:由数字0~9和正负号表示.如 123,-456,0 n八进制整数:由...

    程序运行过程中,其值保持不变的量叫常量。

    按照所属数据类型分四种常量:

    整形常量

    实行常量

    字符串常量

    字符型常量

    一、整型常量

    三种形式:
    n十进制整数:由数字0~9和正负号表示.如 123,-456,0
    n八进制整数:由数字0开头,后跟数字0~7表示.如0123,011
        //可能无意中布下陷阱,如case分支用0补齐
    n十六进制整数:由0x开头,后跟0~9,a~f,A~F表示如0x123,0Xff

    二、实型常量

    •表示形式(两种):
    –十进制数形式:(必须有小数点) 如:0.123, .123, 123.0
    –指数形式:(eE之前必须有数字指数必须为整数)12.3e3 ,123E2, 1.23e4。
    错误的写法:e-5 ,1.2E-3.5,e3。
    •实型常量的类型:
    –默认为double
    实型常量后加字母fF,认为它是float,如12.3f
    实型常量(浮点数)使用注意事项
    int i = 0;
    if( 0 == i) { printf(OK); }//整形比较大小
    浮点数如何比较大小?
    查看以下打印结果(完全代码见备注页):
    int i=  0;
    float f = 0.0;
    for ( i=0; i<26; i++) f+= 0.1;
    printf ( "%d,%.7f,%f/n", i, f ,f);
    所以浮点数很少是精确的!浮点数比较大小只要是足够接近即可
    if( f-2.6>-1e-6 && f-2.6 <1e-6) { printf("OK");   }
    //这里用if( f == 2.6f)是不合理的!
    正确的方法:
    #include <stdio.h>
    void main()
    {
    int i=  0;
    float f = 0.0;
    for ( i=0; i<26; i++) f+= 0.1f;
    printf ( "%d,%.7f,%f/n", i, f ,f);
    if( (f-2.6)>-1e-6 && (f-2.6)<1e-6 )
    {
    printf("ok/n");
    }
    }
    三、字符常量
    定义:用单引号括起来的单个普通字符转义字符
    转义字符
    控制字符,表示特殊的动作,不能在屏幕上显示,反斜线后面跟一个字符或一个代码值表示。
    字符常量的值:一个字符其实是用一个8位整数表示的,故可以表示256种字符, 表示字符的整数就是ASCII码。
    附:常见转义字符
    /n---回车换行 
    /a---警报
    /b---退格
    /r---回车不换行,当前位置移到本行行首
    //---反斜杠
    /’---单引号
    /”---双引号
    /ddd ---八进制值(d表示一个八进制数字)
    如 /101 表示10进制的65
    /xhh ---十六进制值(h表示一个十六进制数字)
    如 /x41表示10进制的65
    上面两个当我们想打印ASCII码对应的字符时可用。
    四、字符串常量
    字符串常量
    定义:用双引号(“ ”)括起来的字符序列。
    存储:每个字符串尾自动加一个 ‘/0’作为字符串结束标志
    例如:字符串“hello”在内存中。
    用空格隔开的连续的字符串被视为一个串。
    例如:查看程序运行结果
    void main( )
    {
        char str[ ] = "Neu"  "soft";
        printf("%d/n",sizeof(str));
        printf("%s/n",str);
    }


    除了按照所属数据类型,常量分为四种,
    从表现形式上看常量有下面几种形式:
    1.字面常量-1,3,4,…… ‘a’,’b’,’c’,“hello”
    2.宏常量-#define  PRICE  30
    3.const常量- const  int  price = 30;
    4.枚举常量—使用enum关键字

    使用宏常量的好处:
    1.- 增加程序可读性;
    2.- 减少书写错误;
    3.- 便于维护;

    使用const而不是宏来定义常量的好处:
    1、const定义的常量编译器可以对其进行数据类型安全检查,而#define宏定义的常量却只是进行简单的字符替换,且有时还会产生边际效应(不如你愿)。
    所谓边际效应举例如下:
                #define N 100
                #define M 200 + N
                当程序中使用 M*N 时,原本想要 100 * (200+ N )的却变成了 100 * 200 + N。
    2、有些调试程序可对const进行调试,但不对#define进行调试。
    3、当定义局部常量时,const作用域仅限于定义局部常量的函数体内。但用#define时其作用域不仅限于定义局部常量的函数体内,而是从定义点到整个程序的结束点。
    使用枚举常量
    枚举常量一定是整形量
    枚举常量不能取地址

    展开全文
  • 这个版本更新的dialog在app中封装的是ok的,为什么到这里就LIbs中就不能用了,黄油刀用的是注解的方式去找我们的layout中的id所以要求必须为 final 的模式,看下APP中生成的R文件这是Android lib中的R文件所以在Android...

    抽取通用模块的时候,自定义view中的引用全部是黄油刀来绑定的,但是抽取到AndroidLib的时候,出现了如下的错误;


    这里写图片描述


    这个版本更新的dialog在app中封装的是ok的,为什么到这里就LIbs中就不能用了,


    黄油刀用的是注解的方式去找我们的layout中的id

    所以要求必须为 final 的模式,


    看下APP中生成的R文件

    这里写图片描述

    这是Android lib中的R文件

    这里写图片描述


    所以在Androidlibs里面还是自己写 findViewByID()


    疑问点:为什么宿主app生成的R文件是final的,而Libs的R文件是非final的哪位能解答下,谢谢!

    展开全文
  • 文本字符串常量的类型是常量字符数组,之所以可以初始化一个char *字符指针变量,是为了兼容 以前C的语法,兼容旧的程序 数组类型 = 元素类型+各维的维数,这才是各完整的数组的类型 可以使用sizeof()...
  • 错误: 元素值必须为常量表达式

    千次阅读 2018-04-19 20:23:38
    出现上诉问题只需要导入 butterknife 包中的 R2 就可以 比如R2.id.XXX,另外还需要将switch 改成 if else 判断
  • 遇到一个问题,初始值设定元素不是常量。[tshua@myarch list]$ make cc -c -o main.o main.c main.c:7:16: 错误:初始值设定元素不是常量 node *header = (node*) malloc(sizeof(node)); ^ make: *** [<...
  • 数组常量

    千次阅读 2018-03-26 10:34:24
    如果数组值已经提前知道了,那么可以通过 数组常量 的方法来初始化数组,而不用依次使用 []= 方法(所有的组成元素都有相同的常量语法)。 package main import "fmt" func main() { var arrKeyValue ...
  • 什么推荐使用枚举来代替int常量

    千次阅读 2019-01-06 20:22:03
    枚举类型(enum type) 是指一组固定的常量组成合法值的类型,例如一年中的季节、太阳系中的行星或者一副牌中的花色。 int枚举模式的缺点 在我们平常的开发中,为表示同种类型的不同种类,经常的做法是声明一组具名...
  • 常量数组

    千次阅读 2019-02-22 22:48:10
    可以用来解决一些有有限个元素具有某些性质的问题,把这有限个元素放到一个常量数组里 敲错键盘的打字员 https://vjudge.net/problem/UVA-401 char ch,a[]=&quot;`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'...
  • 控制台显示错误:元素值必须为常量表达式。为什么会这样?仔细查看发现在library中注解生成的R文件里是没有final标识的,正常是有的,导致识别不了R文件。好在butterknife在8.0之后提供了解决的办法 ...
  • 顾名思义,Java 中的字符常量串池( String Pool )是一个存储在 Java 堆内存中的字符串池。 我们知道 String 是 java 中的特殊类,我们可以使用 new 操作创建 String 对象,也可以使用双引号来创建 String 对象. ...
  • 即是数组第一个元素的地址。 指向常量的指针:在指针定义语句的类型前加const,表示指向的对象是常量。如:const int *pi=&count;(*pi指针指向的值不能修改,但是地址可以被修改,即可以将指针指向另一个常量) ...
  • 常量指针和指针常量

    2010-09-15 11:10:00
    我们先定义了一个字符数组,它有五个元素,我们用一个常量的字符串对它进行了赋值,要注意的是,这种赋值形式只能在数组定义的同时进行,为什么这里我们定义的5个元素则不是4个元素的字符数组呢?要注意,对于常量...
  • 定义常量Map

    千次阅读 2019-03-23 16:20:15
    我们知道在java编程中,定义常量时必须用到:static final,既是静态的,又是不可修改的; 普通类型的常量可以这么定义,但是对于集合类型的常量,我们该怎么定义才好用呢? 本文就来讲一下常量Map的定义,如有...
  • C中字符串常量&字符数组&字符常量

    千次阅读 多人点赞 2019-12-17 11:21:42
    属于四大基本数据类型(分别是整型,浮点型,指针,聚合类型)中的整型(integers)。 C语言字符常量与字符串常量 备注:字符常量和字符变量: 字符常量字符常量是用单引号括起来的一个字符,...
  • 常量:C++包括两种常量,字面常量和符号常量. 字面常量:指的是直接输入到程序中的值 比如:in myAge=26;myAge是一个int类型变量,而26是一个字面常量。 符号常量:指的是用名称表示的常量,就像变量一样表示...
  • 指针常量常量指针

    千次阅读 2008-08-22 14:23:00
    一:指针常量常量指针常量指针:常量指针就是指向常量的指针,指针所指向的地址的内容是不可修改的。指针常量定义"const int * pi=&a;"告诉编译,*pi是常量,不能将*pi作为左值进行操作。所以这里的指针还是一...
  • C语言 枚举常量

    千次阅读 2019-06-26 20:43:56
    枚举型是一个集合,集合中的元素(枚举成员)是一些命名的整型常量元素之间用逗号,隔开。 枚举常量的定义格式: enum 枚举名{枚举名 = 值,枚举名 = 值,枚举名 = 值…}; 也可以不赋值,他默认会从0开始递增 enum ...
  • 那么‘a’和“a”有什么区别呢? C语言编译系统在处理字符串时,在每一个字符串常量的结尾加上一个字符‘\0’,作为字符串结束的标志。‘\0’是一个ASCII码为0的字符,是“空操作字符”,它不引起任何控制操作,也...
  • 告诉你一个小秘诀:指针常量常量指针如何区别, =》把其*读作指针,把其const...我们先定义了一个字符数组,它有五个元素,我们用一个常量的字符串对它进行了赋值,要注意的是,这种赋值形式只能在数组定义的同时进行
  • C语言字符常量与字符串常量

    万次阅读 多人点赞 2019-02-28 09:51:03
    字符常量:一个用单引号括起来的单个字符(或字符转义序列或三字母词) 实质(含义):是一个整形值。属于四大基本数据类型(分别是整型,浮点型,指针,聚合类型)中的整型(integers)。 如‘a', '\n', '??!', ...
  • 以上代码有什么问题? 首先,编译器在常量区保存一个字符串abcd,其内容不许修改。 char *p = "abcd"; // p是一个指针,直接指向常量区,修改p[0]就是修改常量区的内容,这是不允许的。 char p[] = "abcd"; // ...
  • // p是一个指针,直接指向常量区,修改p[0]就是修改常量区的内容,这是不允许的。 char p[] = "hello"; // 编译器在栈上创建一个字符串p,把abcd从常量区复制到p,修改p[0]就相当于修...
  • 1、在汉语中,定语一般都放在中心词的前面,像C语言和C++语言这种技术性语言,更是如此。 所以定语重要还是中心词重要,肯定是中心词重要。 ...指针常量:指针是定语,常量是中心词,所以指针常...
  • 我们先定义了一个字符数组,它有五个元素,我们用一个常量的字符串对它进行了赋值,要注意的是,这种赋值形式只能在数组定义的同时进行,为什么这里我们定义的5个元素则不是4个元素的字符数组呢?要注意,对于常量...
  • PHP 常量与变量

    千次阅读 2015-06-25 06:52:27
    常量】  可以用 define() 函数来定义常量,在 PHP 5.3.0 以后,可以使用 const 关键字在类定义之外定义常量。一个常量一旦被定义,就不能再改变或者取消定义。   常量只能包含标量数据(boolean,integer,...
  • 常量指针与指向常量的指针

    千次阅读 2016-06-30 08:58:36
    const int *p和int * const p的区别 对于指针和常量,有以下三种形式都是正确的: const char * myPtr = &char_A;//指向常量的指针 ...//常量的指针 ...//指向常量常量指针 下面依次对这三种类型
  • Matlab中的常量

    千次阅读 2013-10-05 00:45:43
    Matlab中的常量 1、i,j:虚数单位 >> i ans = 0 + 1.0000i >> j ans = 0 + 1.0000i 2、pi:圆周率π >> pi ans = 3.1416 3、NaN,表示不定值 >> 0/0 ans = NaN 4、inf:无穷大
  • 出现“未结束的字符串常量”脚本错误示例:解决方案:增加meta元素 Ext.onReady(function(){ Ext.BLANK_IMAGE_URL = ../../extjs2.0/resources/images/default/s.gif; Ext.QuickTips.init();//使用HtmlEditor之前...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 291,657
精华内容 116,662
关键字:

属于常量元素的是什么