6366

帖子

4914

TA的资源

版主

21
 
词法分析,让状态机旋转地更猛烈些吧----小话c语言(21)
作者:陈曦
日期:2012-6-1610:21:31
环境:[Mac 10.7.1Lion Intel-based x64 gcc4.2.1 xcode4.2]
转载请注明出处
Q对于编译过程的词法分析,到底应该使用什么方式?
A可以肯定的是,必然要对需要处理的数据挨个字符判断,然后在恰当的位置截断,得到一个个的token.
Q为什么得挨个字符都判断?
A因为编码采用源代码的方式,你无法判断程序员下一个字符是什么。比如inti;int1i; 这两种语句显然含有不同的符号。
Q如何进行词法分析?
A一种很简单的思路就是,用一个状态保存在处理到各个字符时的状态,比如是标识符或者数字或者空格等等,直到状态改变到可以认定是不同token的时候结束。
Q给个设计图吧。
A现在不用看设计图,它来源于如下的实践。
[cpp] view plaincopy
cur_state = STATE_UNKNOWN;  
state = STATE_START;  
if(isdigit(*buf++))   
{  
   state = STATE_NUM;   
   continue;  
}  
if(isblank(*buf++) && state ==STATE_NUM)   
{  
   cur_state = STATE_NUM;   
   state = STATE_BEGIN;  
   continue;  
}  
其实,这里的核心在于将不同符号对应的字符给区别开,在一个字符无法表达此符号时将它截断,token形成。
Q是否在token类型变多的情形下,上面的代码将变得很复杂?
A是的。token类型很多,截断token的可能和条件将变多,必然要进行恰当处理才能正确截断token,这是个内部复杂但不难的过程,可能需要分离复杂到子模块中。
Q可以尝试写代码了吧。
A是的。如下是对上面描述的状态机的结构定义:
[cpp] view plaincopy
typedef struct  
{  
   char            *buf;  
   char            *begin;  
   char            *cur;  
   char            *end;  
     
   Lex_state       state;  
   Lex_sub_state   sub_state;  
}Token_state;  
对于Lex_stateLex_sub_state的定义如下:
[cpp] view plaincopy
typedef enum  
   {  
       Lex_state_begin,  
       Lex_state_id,  
       Lex_state_literal_num,  
       Lex_state_literal_char,  
       Lex_state_literal_str,  
       Lex_state_op,  
       Lex_state_end,  
         
       Lex_state_err  
   }Lex_state;  
     
   typedef enum  
   {  
       Lex_sub_state_begin,  
       Lex_sub_state_underline,  
       Lex_sub_state_alpha,  
       Lex_sub_state_dec_num,  
       Lex_sub_state_oct_num,  
       Lex_sub_state_hex_num,  
       Lex_sub_state_op,  
       Lex_sub_state_quot,  
       Lex_sub_state_space,  
       Lex_sub_state_semi,  
       Lex_sub_state_literal_char_begin,
       Lex_sub_state_literal_char_end,  
       Lex_sub_state_literal_str_begin,  
       Lex_sub_state_literal_str_end,  
         
       Lex_sub_state_end  
   }Lex_sub_state;  
Q对于最终分析出来的token,用什么结构保存?
A先做个简单的结构:
[cpp] view plaincopy
typedef struct  
{  
   char        *name;  
   Token_type  type;  
}Token;
  
typedef struct  
{  
   Token       **p;  
   int         size;  
   int         capacity;  
}TokensTable;  
Token是单一的符号,TokensTable是数个Token的集合。对于,Token_type定义如下:
[cpp] view plaincopy
typedef enum  
{  
   Token_type_keyword,  
   Token_type_var,  
   Token_type_literal,  
   Token_type_operator,  
     
   Token_type_err  
     
}Token_type;  
Q具体代码如何写?
A首先,我们得实现创建、销毁Token以及TokensTable的代码。
[cpp] view plaincopy
Token *createToken(const char *name)  
{  
   Token *tk = (Token *)malloc(sizeof(Token));  
   if(!tk)  
       return NULL;  
   size_t len = strlen(name);  
   char *nameCp = (char *)malloc(len + 1);
   if(!nameCp)  
       goto end;  
   strcpy(nameCp, name);  
   tk->name = nameCp;  
   tk->type = Token_type_err;  
   return tk;  
end:
   free(tk);  
   return NULL;  
}  
  
Token *createTokenByLen(const char *name,size_t len)  
{  
   Token *tk = (Token *)malloc(sizeof(Token));  
   if(!tk)  
       return NULL;  
   char *nameCp = (char *)malloc(len + 1);
   if(!nameCp)  
       goto end;  
   strncpy(nameCp, name, len);  
   tk->name = nameCp;  
   tk->type = Token_type_err;  
   return tk;  
end:
   free(tk);  
   return NULL;  
}  
  
void printTokenName(Token *tk)  
{  
   printf("%p tokenName is %s\n", tk, tk->name);  
}  
  
void freeToken(Token *tk)  
{  
   free(tk->name);  
   free(tk);  
}  
Q为什么没把token的类型当参数传进来?
A因为,这里为了更单一化,将token的类型放到另一个过程去实现。
Q对于TokensTable的实现:
[cpp] view plaincopy
TokensTable *createTokensTable(int defaultSize)
{  
   TokensTable *tt = (TokensTable *)malloc(sizeof(TokensTable));  
   if(!tt)  
       return NULL;  
     
   Token    **p = (Token**)malloc(sizeof(Token *) * defaultSize);
   if(!p)  
       goto err_has_malloc_tt;  
   tt->p = p;  
   tt->capacity = defaultSize;  
   tt->size = 0;  
   return tt;  
     
err_has_malloc_tt:  
   free(tt);  
   return NULL;  
}  
  
  
bool addTokenToTable(TokensTable *tt, constToken *token)  
{  
   Token *tk = (Token *)malloc(sizeof(Token));  
   if(!tk)  
       return false;  
   tk->name = (char *)malloc(strlen(token->name) + 1);  
   if(!tk->name)  
       goto end;  
   strcpy(tk->name, token->name);
   tk->type = token->type;  
   if(tt->size < tt->capacity)
   {  
       tt->p[tt->size] = tk;  
       ++tt->size;  
   }  
   else    // alloc a biggermemory  
   {  
       int i;  
       Token **temp = (Token **)malloc(sizeof(Token *) * (tt->capacity +128));  
       if(!temp)  
           goto should_free_name;  
       for(i = 0; i < tt->capacity; ++i)
       {  
           temp = tt->p;  
       }  
       tt->capacity += 128;  
       free(tt->p);  
         
       tt->p = temp;  
       tt->p[tt->size] = tk;  
       ++tt->size;  
   }  
   return true;  
     
should_free_name:      
   free(tk->name);  
end:
   free(tk);  
   return false;  
}  
  
  
void removeTokenAt(TokensTable *tt, intindex)  
{  
   Token *tk = (Token *)tt->p[index];
   freeToken(tk);  
   tt->p[index] = NULL;  
}  
  
void removeLastToken(TokensTable *tt)  
{  
   removeTokenAt(tt, tt->size - 1);
}  
  
void   freeTokensTable(TokensTable *tt)  
{  
   int i = 0;  
   for(; i < tt->size; ++i)  
   {  
       if(tt->p)  
           free(tt->p);  
   }  
   free(tt->p);  
   free(tt);  
}  
  
void showAllTokens(TokensTable *tt)  
{  
   int i = 0;  
   printf("TokensTable:%p All tokens: ", tt);  
   for(; i < tt->size; ++i)  
   {  
       if(tt->p)  
       {  
           printf("Token %d:%s \n", i + 1, ((Token*)tt->p)->name);     
       }  
   }  
   printf("\n");  
}  
A好的,现在可以实现状态机了。
[cpp] view plaincopy
Token_state *  
    createTokenState(char *buf);  
  
bool
getAllTokens(Token_state *ts, TokensTable*tk);  
  
void
   freeTokenState(Token_state *ts);  
Q实现代码:
[cpp] view plaincopy
Token_state *createTokenState(char*buf)  
{  
   Token_state *state = (Token_state *)malloc(sizeof(Token_state));  
   if(!state)  
       return NULL;  
   size_t len = strlen(buf);  
   char    *bufCp = (char*)malloc(len + 1);  
   if(!bufCp)  
       goto end;  
   strcpy(bufCp, buf);  
   state->buf = state->begin = state->cur = bufCp;  
   state->end = bufCp + len;  
   state->state = Lex_state_begin;
   state->sub_state = Lex_sub_state_begin;  
   return state;  
end:
   free(state);  
   return NULL;  
}  
  
bool getAllTokens(Token_state *ts,TokensTable *tt)  
{  
   cc_skip_space((const char **)&ts->cur);  
   while(*ts->cur)  
   {  
       if(isalpha(*ts->cur))  
       {  
           if(ts->state == Lex_state_begin || ts->state == Lex_state_id)  
           {  
                ts->state = Lex_state_id;  
                ts->sub_state =Lex_sub_state_alpha;  
                goto will_continue;  
           }  
           else if((*ts->cur >= 'a' && *ts->cur <= 'f')  
                    || (*ts->cur >= 'A'&& *ts->cur <= 'F'))  
           {  
                if(ts->sub_state ==Lex_sub_state_hex_num)  
                {  
                    goto will_continue;  
                }  
           }  
           else  
           {  
                  
           }  
       }  
       else if(*ts->cur == '_')  
       {  
           if(ts->state == Lex_state_begin || ts->state == Lex_state_id)  
           {  
                ts->state =Lex_state_id;  
                ts->sub_state =Lex_sub_state_underline;  
                goto will_continue;  
           }  
       }  
       else if(isdigit(*ts->cur))  
       {  
           if(ts->state == Lex_state_begin)
           {  
                if(*ts->cur == '0')  
                {  
                    ts->state =Lex_state_literal_num;  
                    ts->sub_state =Lex_sub_state_oct_num;  
                    goto will_continue;  
                }  
                else  
                {  
                    ts->state =Lex_state_literal_num;  
                    ts->sub_state =Lex_sub_state_dec_num;  
                    goto will_continue;  
                }  
           }  
           else if(ts->sub_state == Lex_sub_state_dec_num)  
           {  
                ts->state =Lex_state_literal_num;  
                ts->sub_state =Lex_sub_state_dec_num;  
                goto will_continue;  
           }  
           else if(ts->sub_state == Lex_sub_state_oct_num)  
           {  
                if(*ts->cur >= '8')  
                {  
                    error_process(ts,Error_type_oct_include_over_7_num);  
                    return false;  
                }  
                else  
                {  
                    ts->state =Lex_state_literal_num;  
                    ts->sub_state =Lex_sub_state_oct_num;  
                    goto will_continue;  
                }  
           }  
           else if(ts->state == Lex_state_id)
           {  
                goto will_continue;  
           }  
           else if(ts->state == Lex_sub_state_hex_num)  
           {  
                goto will_continue;  
           }  
           else  
           {  
                  
           }  
       }  
       else if(isspace(*ts->cur) || *ts->cur == ';' /*||isOpeChar()*/)  
       {  
            if(*ts->cur == *ts->begin)  
           {  
                ++ts->cur;  
                continue;  
           }  
           // save the previous token  
           Token *temp_tk = createTokenByLen(ts->begin, ts->cur -ts->begin);  
           if(!temp_tk)  
           {  
                error_process(ts,Error_type_not_enough_mem);  
                return false;  
           }  
           else  
           {  
                addTokenToTable(tt,temp_tk);  
                freeToken(temp_tk);  
                  
                cc_skip_space((const char**)&ts->cur);  
                ts->begin = ts->cur;  
                continue;  
           }  
       }  
         
   will_continue:  
       ++ts->cur;  
       continue;  
   }  
   if(*ts->cur == '\0')  
   {  
       // save the previous token  
       Token *temp_tk = createTokenByLen(ts->begin, ts->cur -ts->begin);  
       if(!temp_tk)  
       {  
           error_process(ts, Error_type_not_enough_mem);  
            return false;  
       }  
       else  
       {  
           addTokenToTable(tt, temp_tk);  
           freeToken(temp_tk);  
       }  
   }  
     
   return true;  
}  
  
  
void freeTokenState(Token_state *ts)  
{  
   free(ts->buf);  
   free(ts);  
}  
A测试代码如下:
[cpp] view plaincopy
void   testLexParser()  
{   
#if 1
   // char    *str ="0812";  
   // char    *str ="012";  
   char    *str = "int i =12;";  
     
   Token_state *ts = createTokenState(str);
    if(!ts)  
   {  
       printf("create Token_state error\n");  
       return;  
   }  
     
   TokensTable *tt = createTokensTable(1);
   if(!tt)  
   {  
       printf("create TokensTable error\n");  
       freeTokenState(ts);  
       return;  
   }  
     
   if(getAllTokens(ts, tt))  
   {  
      //printTokenName(tt->p[0]);  
      showAllTokens(tt);  
   }  
     
   freeTokensTable(tt);  
   freeTokenState(ts);  
     
#endif
}  
[cpp] view plaincopy
int main(int argc, const char *argv[])  
{  
#if 1
   testLexParser();  
#endif
   return 0;  
}  
运行结果:
[plain] view plaincopy
TokensTable:0x252810 All tokens: Token1:int   
Token 2:i  
Token 3:=  
Token 4:12  
Token 5:;  
当然,状态机判断逻辑看起来很复杂,如果需要,可以继续封装。
cc_skip_space的代码如下:
[cpp] view plaincopy
void   cc_skip_space(const char **p)  
{  
   while (isspace(**p))  
   {  
       (*p)++;  
   }  
}  
作者:陈曦
日期:2012-6-1610:21:31
环境:[Mac 10.7.1Lion Intel-based x64 gcc4.2.1 xcode4.2]
转载请注明出处


[ 本帖最后由 tiankai001 于 2012-12-15 08:01 编辑 ]
此帖出自单片机论坛
 

回复

6366

帖子

4914

TA的资源

版主

22
 
作者:陈曦
日期:2012-6-23  14:24:27
环境:[Mac 10.7.1 LionIntel i3 支持64位指令 gcc4.2.1xcode4.2]  
转载请注明出处
Q1: 编译生成的可执行文件内部是如何被执行的?
A: 当然,首先需要知道它的结构。先编写一个简单的程序,保存为testForC.c:
1.    #include   
2.    #include   
3.      
4.    #define PRINT_D(longValue)       printf(#longValue" is %ld\n", ((long)longValue));  
5.    #define PRINT_STR(str)              printf(#str" is %s\n", (str));  
6.      
7.      
8.    int main()  
9.    {  
10.      printf("hello\n");  
11.   
12.      return 0;  
13.  }  


编译生成32位应用程序testForC.使用file命令得到它的信息:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image002.gif
Q2: Mach-O是什么?
A: 它表示mach架构下的目标文件。我们可以通过工具MachOView来查看:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image004.gif
由上图可以看到mach-o格式开头的基本信息,包括开头的魔数、对应CPU类型等信息。
Q3: 这些结构在哪里可以看到?
A: 在mach-o/arch.h头文件可以看到如下结构:
1.    typedef struct {  
2.        const char *name;  
3.        cpu_type_t cputype;  
4.        cpu_subtype_t cpusubtype;  
5.        enum NXByteOrder byteorder;  
6.        const char *description;  
7.    } NXArchInfo;  

它包含一些架构信息,在mach-o/loader.h头文件中包含mach-o格式文件头的格式:
1.    /*
2.     * The 32-bit mach header appears at the very beginning of the object file for
3.     * 32-bit architectures.
4.     */  
5.    struct mach_header {  
6.        uint32_t    magic;      /* mach magic number identifier */  
7.        cpu_type_t  cputype;    /* cpu specifier */  
8.        cpu_subtype_t   cpusubtype; /* machine specifier */  
9.        uint32_t    filetype;   /* type of file */  
10.      uint32_t    ncmds;      /* number of load commands */  
11.      uint32_t    sizeofcmds; /* the size of all the load commands */  
12.      uint32_t    flags;      /* flags */  
13.  };  

1.    /* Constant for the magic field of the mach_header (32-bit architectures) */  
2.    #define MH_MAGIC    0xfeedface  /* the mach magic number */  
3.    #define MH_CIGAM    0xcefaedfe  /* NXSwapInt(MH_MAGIC) */  



Q4: 上面的cputype是CPU_TYPE_I386表示的是32位吧,有的应用程序包含32位和64位体系的代码,结构是如何的?
A: 先生成一个这样的文件。依然使用文章开头的代码,使用如下命令编译:
1.    gcc -o testForC testForC.c -arch i386 -arch x86_64  


继续使用file命令得到它的信息:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image006.gif
可以看出,它包含两种体系代码。我们同样使用MachOView工具查看它的内部信息:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image008.gif
可以看出,它内部包含了两种架构的信息,并且它的开头有一些特定的信息,之后才是不同架构的信息。
Q5: 上图中的Load Commands表示什么?
A: 它记录了文件中各个段(如代码段,数据段等)如何映射到内存,也包含了文件使用的动态链接器以及文件需要的动态库,同时也包含文件和动态链接器使用的符号表。Load Commands对应的结构定义如下:
1.    /*
2.     * The segment load command indicates that a part of this file is to be
3.     * mapped into the task's address space.  The size of this segment in memory,
4.     * vmsize, maybe equal to or larger than the amount to map from this file,
5.     * filesize.  The file is mapped starting at fileoff to the beginning of
6.     * the segment in memory, vmaddr.  The rest of the memory of the segment,
7.     * if any, is allocated zero fill on demand.  The segment's maximum virtual
8.     * memory protection and initial virtual memory protection are specified
9.     * by the maxprot and initprot fields.  If the segment has sections then the
10.   * section structures directly follow the segment command and their size is
11.   * reflected in cmdsize.
12.   */  
13.  struct segment_command { /* for 32-bit architectures */  
14.      uint32_t    cmd;        /* LC_SEGMENT */  
15.      uint32_t    cmdsize;    /* includes sizeof section structs */  
16.      char        segname[16];    /* segment name */  
17.      uint32_t    vmaddr;     /* memory address of this segment */  
18.      uint32_t    vmsize;     /* memory size of this segment */  
19.      uint32_t    fileoff;    /* file offset of this segment */  
20.      uint32_t    filesize;   /* amount to map from the file */  
21.      vm_prot_t   maxprot;    /* maximum VM protection */  
22.      vm_prot_t   initprot;   /* initial VM protection */  
23.      uint32_t    nsects;     /* number of sections in segment */  
24.      uint32_t    flags;      /* flags */  
25.  };  


比如说,对于testForC的第一个截图:
_TEXT段的VM Address为0x1000.
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image010.gif
对于section __text, 它对应的地址是:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image012.gif
由此,我们可以得到__text在文件中的真实位置为: 0x1F10  - 0x1000  =  0xF10.
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image014.gif
作者:陈曦
日期:2012-6-23  14:24:27
环境:[Mac 10.7.1 LionIntel i3 支持64位指令 gcc4.2.1xcode4.2]  
转载请注明出处
5/25/13D�B81�2g"/>file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image024.gif
再运行file命令:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image026.gif
可以看到,文件包含了两种架构体系代码。
Q: 看到很多逆向工具,可以修改特定的目标文件实现特定的目的,是否可以修改hello文件来得到希望的执行效果?
A: 是的。我并不善于修改汇编代码,我们可以修改数据段来查看效果。
修改上面的hello.c代码如下:
1.     #include   
2.      
3.     int g_i = 0xAA;  
4.     const int g_j = 0xBB;  
5.     char *str = "hello";  
6.      
7.     int main()  
8.     {  
9.         int i = 2;  
10.      printf("%s %d\n", str, i);  
11.      return 0;  
12.  }  


编译成hello.并查看执行效果:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image028.gif
下面就使用vi来修改可执行文件的hello字符串为iello,最后查看执行效果。使用vi打开hello文件,定位到hello字符串的位置:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image030.gif
修改hello的h为i:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image032.gif
保存,然后执行hello:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image034.gif
可以看到,执行结果已经变了。
Q: 有时,需要查看一个目标文件中各个段的大小,如何查看?
A: 可以使用size命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image036.gif
Q: 有时,需要查看目标文件中可打印的字符串有哪些,如何查看?
A: 可以使用strings命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image038.gif
Q: 很多编译好的程序,里面包含了符号表或者一些调试信息,如何去除它们?
A: 可以使用strip命令。
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image040.gif
使用strip命令后:
file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image042.gif
可以看到,符号信息确实少了。
Q: 上面提到的nm命令,它是做什么的?
A: 它是可以查看目标文件中包含的符号信息的。
xichen
2012-5-2512:50:52


[ 本帖最后由 tiankai001 于 2012-12-15 08:02 编辑 ]
此帖出自单片机论坛
 
 

回复

6366

帖子

4914

TA的资源

版主

23
 
作者:陈曦
日期:2012-7-2812:20:17
环境:[Mac 10.7.1 LionIntel i3 支持64位指令 gcc4.2.1xcode4.2]  
转载请注明出处
Q1: 可变参数的函数调用能够被正确执行的本质原因是什么?
A: 可变参数的一个重要特点就是参数个数不确定,但是最终可以被正确执行一般需要堆栈以及参数类型的确定性支持。如果参数类型都无法确定是某种或者某个范围内,那么可变参数函数是无法实现的。
Q2: 举个可变参数的例子吧。
A: 比如,求一组整形数的平均数:
      get_average(2, 100,  1000),  第一个参数表示求平均数的整数个数为2个,后面跟着2个整数100和1000;
       如果是求3个数的平均数,调用如下:
      get_average(2, 100, 1000,  200).
      代码如下:
1.    #include   
2.    #include   
3.    #include   
4.    #include   
5.      
6.    #define PRINT_D(longValue)       printf(#longValue" is %ld\n", ((long)longValue));  
7.    #define PRINT_STR(str)              printf(#str" is %s\n", (str));  
8.    #define PRINT_DBL(doubleValue)       printf(#doubleValue" is %f\n", (doubleValue));  
9.      
10.  double get_average(int int_num_count, ...)  
11.  {  
12.      va_list list;  
13.      double sum = 0;  
14.      int int_num_count_bak = int_num_count;  
15.        
16.      va_start(list, int_num_count);  
17.      while(int_num_count > 0)  
18.      {  
19.          sum += va_arg(list, int);  
20.          --int_num_count;  
21.      }  
22.        
23.      va_end(list);  
24.      return sum / int_num_count_bak;  
25.  }  
26.   
27.  int main()  
28.  {  
29.      double ret = get_average(2, 3, 4);  
30.      PRINT_DBL(ret)  
31.        
32.      ret = get_average(3, 3, 4, 5);  
33.      PRINT_DBL(ret)  
34.        
35.      return 0;  
36.  }  

Q3: get_average函数原型最后的...就是表示可变参数?
A: 是的。也可以参考printf函数的原型:
1.    int  printf(const char * __restrict, ...) __DARWIN_LDBL_COMPAT(printf) __printflike(1, 2);  

Q4: va_list, va_start, va_arg和va_end,它们实现了什么?
A: 它们分别实现了从堆栈中获取参数的首地址,依次获取不同参数的地址,最后结束处理的操作。
Q5: va_list等几个类型和函数(或者宏)的内部是如何实现的?
A: 在mac下,它们被宏定义为另外一个类型,内部的具体实现没有公开。下面将windows的实现列出(部分代码):
1.    #ifndef _VA_LIST_DEFINED  
2.    #ifdef  _M_ALPHA  
3.    typedef struct {  
4.            char *a0;       /* pointer to first homed integer argument */  
5.            int offset;     /* byte offset of next parameter */  
6.    } va_list;  
7.    #else  
8.    typedef char *  va_list;  
9.    #endif  
10.  #define _VA_LIST_DEFINED  
11.  #endif  
12.   
13.  #ifdef  _M_IX86  
14.   
15.   
16.  #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )  
17.   
18.  #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )  
19.  #define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )  
20.  #define va_end(ap)      ( ap = (va_list)0 )  
21.   
22.  #elif   defined(_M_MRX000)  


可以看出,va_list就是一个类似char *的结构,保存参数的首地址(可能还包含下一个参数的偏移)信息;
va_start会定位到第一个可变参数的地址位置;
va_arg会根据参数类型获得此参数的值;
va_end做一个简单的置空操作,作为一个标志。
Q6: c语言函数的默认调用方式采用__cdecl,是否也间接支持了可变参数的实现?
A: 是的。再比如arm平台,在参数较少的情况下,参数是被优先传入寄存器的;如果超过一定的个数,那么参数采用入栈的方式。那么,对于上面的代码,在arm平台到底使用寄存器还是堆栈呢? 在xcode中创建一个ios工程,将上面的代码加入,编译得到它的arm汇编:
1.    0x0009276c :   sub sp, #12  
2.    0x0009276e :   push    {r4, r7, lr}  
3.    0x00092770 :   add r7, sp, #4  
4.    0x00092772 :   sub sp, #28  
5.    0x00092774 :   mov r4, sp  
6.    0x00092776 :  bic.w   r4, r4, #7  ; 0x7  
7.    0x0009277a :  mov sp, r4  
8.    0x0009277c :  str r3, [r7, #16]  
9.    0x0009277e :  str r2, [r7, #12]  
10.  0x00092780 :  str r1, [r7, #8]  
11.  0x00092782 :  add r1, sp, #20  
12.  0x00092784 :  movs    r2, #0  
13.  0x00092786 :  movt    r2, #0  ; 0x0  
14.  0x0009278a :  vldr    d16, [pc, #104] ; 0x927f6   
15.  0x0009278e :  str r0, [sp, #24]  
16.  0x00092790 :  vstr    d16, [sp, #8]  
17.  0x00092794 :  ldr r0, [sp, #24]  
18.  0x00092796 :  str r0, [sp, #4]  
19.  0x00092798 :  add.w   r0, r7, #8  ; 0x8  
20.  0x0009279c :  str r0, [r1, #0]  
21.  0x0009279e :  str r2, [sp, #0]  
22.  0x000927a0 :  movs    r0, #0  
23.  0x000927a2 :  movt    r0, #0  ; 0x0  
24.  0x000927a6 :  ldr r1, [sp, #24]  
25.  0x000927a8 :  cmp r1, r0  
26.  0x000927aa :  ble.n   0x927d2   
27.  0x000927ac :  ldr r0, [sp, #20]  
28.  0x000927ae :  adds    r1, r0, #4  
29.  0x000927b0 :  str r1, [sp, #20]  
30.  0x000927b2 :  ldr r0, [r0, #0]  
31.  0x000927b4 :  fmsr    s0, r0  
32.  0x000927b8 :  fsitod  d16, s0  
33.  0x000927bc :  vldr    d17, [sp, #8]  
34.  0x000927c0 :  faddd   d16, d17, d16  
35.  0x000927c4 :  vstr    d16, [sp, #8]  
36.  0x000927c8 :  ldr r0, [sp, #24]  
37.  0x000927ca :  add.w   r0, r0, #4294967295 ; 0xffffffff  
38.  0x000927ce :  str r0, [sp, #24]  
39.  0x000927d0 : b.n 0x927a0   
40.  0x000927d2 : vldr    d16, [sp, #8]  
41.  0x000927d6 : flds    s0, [sp, #4]  
42.  0x000927da : fsitod  d17, s0  
43.  0x000927de : fdivd   d16, d16, d17  
44.  0x000927e2 : vmov    r0, r1, d16  
45.  0x000927e6 : subs    r4, r7, #4  
46.  0x000927e8 : mov sp, r4  
47.  0x000927ea : ldmia.w sp!, {r4, r7, lr}  
48.  0x000927ee : add sp, #12  
49.  0x000927f0 : bx  lr  
50.  0x000927f2 : nop  
51.  0x000927f4 : lsls    r0, r0, #0  
52.  0x000927f6 : lsls    r0, r0, #0  
53.  0x000927f8 : lsls    r0, r0, #0  
54.  0x000927fa : lsls    r0, r0, #0  


在这里,我们可以看到,里面使用了堆栈寄存器sp,并从sp相关的内存中取参数操作,可以确定,依然采用堆栈方式传递参数的。
一些看起来动态的东西,在c语言那里是使用较为静态的东西来实现。
作者:陈曦
日期:2012-7-2812:20:17
环境:[Mac 10.7.1 LionIntel i3 支持64位指令 gcc4.2.1xcode4.2]  
转载请注明出处


[ 本帖最后由 tiankai001 于 2012-12-15 08:02 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4914

TA的资源

版主

24
 
预处理,它有时很神奇----小话c语言(24)
作者:陈曦
日期:2012-7-28  18:19:55
环境:[Mac 10.7.1Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2]  
转载请注明出处
Q1宏这个东西真是很奇怪,为什么我想将一句#include代码用宏来替换,却不行?
[cpp] view plaincopy
#define INCLUDE_STDIO   #include  
INCLUDE_STDIO  
  
int main()
{  
   return 0;  
}  
保存为preprocess_header.c
A如果是预处理出了问题,我们可以使用-E查看预处理后的结果,来分析到底哪里出了问题。
Q2由上面看,好像没有什么问题。
A看起来是没有什么问题,问题在于上面的结果是预处理后的结果。编译器编译预处理后的源代码还出现了#include, 编译器是不识别的,所以会报错。换句话说,预处理做预处理的事情,编译器做编译源代码的事情(不过编译器常常被看做包括预处理的功能).预处理器可以处理#include,而编译器根本无法识别#include.
Q3预处理当发现INCLUDE_STDIO符号是#include时为什么没有继续预处理此头文件的内容?
A这就在于c语言标准规定#define定义的符号是进行重新预处理的,但是仅限于#define的符号,遇到#include是不做处理的。
Q4有时,需要测试数据,写了好多相同名称开头的变量,最后进行赋值,有什么好方法?
A这当然需要用到##符号了,它可以正确生成你需要的变量。如下代码:
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(longValue)       printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)              printf(#str" is %s\n",(str));  
  
#define ADD_TO_NUM(sum_name, sum_number,value) \  
(sum_name ## sum_number) += (value);  
  
int main(int argc, char **argv)  
{  
   int sum1 = 10, sum2 = 20;  
   int i = 10;  
   ADD_TO_NUM(sum, 1, i);  
   ADD_TO_NUM(sum, 2, i);  
     
   PRINT_D(sum1)  
   PRINT_D(sum2)  
      
     
   return 0;  
}  
编译运行:
[plain] view plaincopy
sum1 is 20
sum2 is 30
同理,如果需要对c语言的结构,比如堆栈、队列等写不同类型数据的操作函数,可以将类型用作参数做出类似的宏定义。
Q5对于do, while循环,我喜欢反着用,用until的模式,有什么方法吗?
A如下代码:
[cpp] view plaincopy
#define repeat  do  
#define until(x)    while(!(x))
测试代码:
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(longValue)       printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)              printf(#str" is %s\n",(str));  
      
#define repeat  do  
#define until(x)    while(!(x))
  
int main(int argc, char **argv)  
{  
  
   int i = 1;  
   repeat  
   {  
       PRINT_D(i)  
       ++i;  
   }until(i > 10);  
     
   return 0;  
}  
运行结果:
[plain] view plaincopy
i is 1
i is 2
i is 3
i is 4
i is 5
i is 6
i is 7
i is 8
i is 9
i is 10
Q6很多时候,写代码的时候,发现一个关键字或者代码组合老是写,用宏做替换是否是个好的选择?
A只要风险可控就是好选择。比如,register关键字感觉好长,可以用REG代替;无限循环for(;;)也很长,可以用FOREVER代替;switch语句内部的breakcase语句连写也可以用宏CASE代替。
[cpp] view plaincopy
#define REG     register
#define FOREVER for(;;)  
#define CASE    break; case
测试代码:
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(longValue)       printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)              printf(#str" is %s\n",(str));  
      
#define REG     register
#define FOREVER for(;;)  
#define CASE    break; case
  
int main(int argc, char **argv)  
{  
  
   REG int i = 1;  
   FOREVER  
       PRINT_STR("hello")  
   return 0;  
}  
这里还有关于文件读写的例子,发现老是要写fopen,fclose函数,写了好多遍,不如封装在一个通用文件中,用宏是个好选择:
[cpp] view plaincopy
#define FOPEN_COMMON(file_name)    \  
FILE   *fp = fopen((file_name), "r+");  \  
if(!fp) \
{  \  
perror("fopen error");  \  
return -1; \  
}  
  
#define FCLOSE_COMMON   \  
fclose(fp);
总之,有什么代码觉得多的地方,用宏做替换是个好选择,不过要清楚这里面的风险。
Q7既然宏就是字符串的替换,那么是否也可以将一种基本类型定义成另一种基本类型?
A c语言标准并没有规定宏字符串不能是关键字,所以上面的情况是可以的。不过,这样很可能打乱以前的逻辑,可以在某些场合做测试。
[cpp] view plaincopy
#include   
#include   
#include   
#include   
  
#define PRINT_D(longValue)       printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT_STR(str)              printf(#str" is %s\n",(str));  
  
#define double  int  
  
int main(int argc, char **argv)  
{  
   double i = 1.5;  
   PRINT_D(i)  
   return 0;  
}  
可以看到,上面将double字符串定义成int字符串,main函数中的定义i的代码也就变成了int  i = 1.5; 最后的输出结果:
[plain] view plaincopy
i is 1
Q8有时,为了方便输出不同类型的变量,写了如下两个宏:
[cpp] view plaincopy
#define PRINT(longValue)       printf(#longValue" is %ld\n",((long)longValue));  
#define PRINT(str)              printf(#str" is %s\n",(str));  
为什么,再使用PRINT(1) 时会崩溃?
A这在于宏并不像函数一样支持重载,它就是个名称,不会因为仅仅宏参数不一样而导致编译器认为宏不一样。所以,上面两个PRINT宏其实是被编译器看做是同一个宏,还会出现重定义的警告:
[plain] view plaincopy
warning: "PRINT" redefined  
所以,PRINT(1)其实使用的是第二个宏。可以使用预处理命令得到预处理后的结果,首先是源代码,保存为preprocess_macro.c :
[cpp] view plaincopy
#include   
  
#define PRINT(longValue)        printf(#longValue"is %ld\n",(longValue));  
#define PRINT(str)              printf(#str" is %s\n",(str));  
  
int main()
{  
   PRINT(1)  
   return 0;  
}  
使用预处理得到如下:
由上图可以看出,确实使用了PRINT(str)这个宏来替换,所以导致崩溃。
Q9有时,希望可以根据sizeof(int)的数值来做一些提示,为什么下面的代码会出现编译错误?
[cpp] view plaincopy
#if sizeof(int) == 4  
  
#elif sizeof(int) == 8  
#warning   "sizeof(int) == 8"  
#endif
A这个原因在于c语言标准规定预处理根本不能计算sizeof的值,且#if后面的条件表达式必须是整形常量,所以会提示错误。
Q10如果我希望可以控制int类型是4字节或者8字节大小,该怎么办?
A其实,这个不是程序员可以控制的,这是平台确定的。在mac下,gcc4.2.1, i386模式int4字节,x86_64模式int也是4字节; 但是,long类型在i3864字节,x86_64模式是8字节,知道上面这些信息后,最好自定义类型在恰当模式使用自己需要大小的类型。下面是对long类型的测试:
[cpp] view plaincopy
#include   
  
int main()
{  
   printf("%u\n", sizeof(long));
   return 0;  
}  
保存为preprocess_if.c.
gcc -o preprocess_if preprocess_if.c -archi386编译后执行,结果为4.
gcc -o preprocess_if preprocess_if.c -archx86_64编译后执行,结果为8.
作者:陈曦
日期:2012-7-28  18:19:55
环境:[Mac 10.7.1Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2]  
转载请注明出处

[ 本帖最后由 tiankai001 于 2012-12-15 08:02 编辑 ]
此帖出自单片机论坛
 
 
 

回复

6366

帖子

4914

TA的资源

版主

25
 
c语言,有时莫名,有时奇妙----小话c语言(25)
作者:陈曦
日期:2012-8-17 12:53:12
环境:[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2]
转载请注明出处
Q1: 为什么下面的输出不按照代码的顺序显示?
#include   
#include   
  
int main(int argc, char **argv)  
{  
    while(1)  
    {  
        fprintf(stdout, "stdout.");  
        fprintf(stderr, "stderr.");  
        sleep(1);  
    }  
    return 0;      
}  

A: 如果把代码改为如下,输出就按照代码顺序显示了:
#include   
#include   
  
int main(int argc, char **argv)  
{  
    setbuf(stdout, NULL);     // set stdout to no buffer                     
    while(1)  
    {  
        fprintf(stdout, "stdout.");  
        fprintf(stderr, "stderr.");  
        sleep(1);  
    }  
    return 0;      
}  



Q2: 下面的代码中printf函数返回为什么不是1?
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
  
int main(int argc, char **argv)  
{  
    char *str = "我";  
    PRINT_D(printf(str));  
      
    return 0;      
}  


上面的代码保存的编码为UTF-8.
A: 在我的另一篇帖子中已经有详细解释: http://blog.csdn.net/cxsjabcabc/article/details/7528227
如果把代码文件的编码改为其它,答案就出来了: printf返回的是实际输出的字节数。比如http://coolshell.cn/articles/945.html地址的帖子写的是字符数,还有很多地方都不是很清楚得说明是字符数,其实也是一种没有深入调研的模糊解答。
Q3: 下面代码的输出为什么改变了b的数值?
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
  
  
int main(int argc, char **argv)  
{  
    int a = 1;         
    switch(a)         
    {      
        int b = 2;            
        case 1:   
            PRINT_D(b)   
            break;   
        default:   
            PRINT_D(b)   
            break;   
    }   
  
    return 0;      
}  


A: 原因在于c标准规定:switch语句体的复合语句中,位于第一个case或者default标号前面的语句永远不会被执行到。所以,b相当于没有初始化。

另外,我们查看一下汇编,能够得到结论,其实case标号和default标号相当于一个地址,编译器内部会使用jmp, je,jne等语句直接跳到对应位置来执行,而不会分析它前面的初始化过程。
0x00001f10 :  push   %ebp  
0x00001f11 :  mov    %esp,%ebp  
0x00001f13 :  sub    $0x28,%esp  
0x00001f16 :  mov    0xc(%ebp),%eax  
0x00001f19 :  mov    0x8(%ebp),%ecx  
0x00001f1c : movl   $0x0,-0x4(%ebp)  
0x00001f23 : mov    %ecx,-0x8(%ebp)  
0x00001f26 : mov    %eax,-0xc(%ebp)  
0x00001f29 : movl   $0x1,-0x10(%ebp)  
0x00001f30 : xor    %dl,%dl  
0x00001f32 : test   %dl,%dl  
0x00001f34 : jne    0x1f52   
0x00001f36 : jmp    0x1f38   
0x00001f38 : lea    0x1fa0,%eax  
0x00001f3e : mov    -0x14(%ebp),%ecx  
0x00001f41 : mov    %eax,(%esp)  
0x00001f44 : mov    %ecx,0x4(%esp)  
0x00001f48 : call   0x1f7a   
0x00001f4d : mov    %eax,-0x18(%ebp)  
0x00001f50 : jmp    0x1f6a   
0x00001f52 : lea    0x1fa0,%eax  
0x00001f58 : mov    -0x14(%ebp),%ecx  
0x00001f5b : mov    %eax,(%esp)  
0x00001f5e : mov    %ecx,0x4(%esp)  
0x00001f62 : call   0x1f7a   
0x00001f67 : mov    %eax,-0x1c(%ebp)  
0x00001f6a : mov    $0x0,%eax  
0x00001f6f : add    $0x28,%esp  
0x00001f72 : pop    %ebp  
0x00001f73 : ret   


注意main+36和main + 38位置的汇编,两条跳转语句会直接找到标号,忽略了前面的代码。
如果把源代码改为如下,依然不能输出3:
int main(int argc, char **argv)  
{  
    int a = 2;         
    switch(a)         
    {      
        int b = 2;            
        case 1:   
            PRINT_D(b)   
            break;  
        b = 3;  
        case 2:  
            PRINT_D(b)  
            break;  
        default:   
            PRINT_D(b)   
            break;   
    }   
  
    return 0;      
}  



Q4: sizeof运算符里面跟着i++,为什么i没有自增?
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
  
  
int main(int argc, char **argv)  
{  
    int i = 1;  
    PRINT_D(sizeof(i++))  
    PRINT_D(i)  
  
    return 0;      
}  


A: 原因在于sizeof在编译期间自动计算出来它的数值,对于i++, 编译器仅仅看到了它的类型,却没有计算它的数值。看看汇编:
0x00001ef0 :  push   %ebp  
0x00001ef1 :  mov    %esp,%ebp  
0x00001ef3 :  push   %ebx  
0x00001ef4 :  push   %edi  
0x00001ef5 :  push   %esi  
0x00001ef6 :  sub    $0x2c,%esp  
0x00001ef9 :  mov    0xc(%ebp),%eax  
0x00001efc : mov    0x8(%ebp),%ecx  
0x00001eff : mov    $0x0,%edx  
0x00001f04 : lea    0x1fa7,%esi  
0x00001f0a : lea    0x1f94,%edi  
0x00001f10 : mov    $0x4,%ebx  
0x00001f15 : movl   $0x0,-0x10(%ebp)  
0x00001f1c : mov    %ecx,-0x14(%ebp)  
0x00001f1f : mov    %eax,-0x18(%ebp)  
0x00001f22 : movl   $0x1,-0x1c(%ebp)  
0x00001f29 : mov    %edi,(%esp)  
0x00001f2c : movl   $0x4,0x4(%esp)  
0x00001f34 : mov    %edx,-0x20(%ebp)  
0x00001f37 : mov    %ebx,-0x24(%ebp)  
0x00001f3a : mov    %esi,-0x28(%ebp)  
0x00001f3d : call   0x1f6e   
0x00001f42 : mov    -0x1c(%ebp),%ecx  
0x00001f45 : mov    -0x28(%ebp),%edx  
0x00001f48 : mov    %edx,(%esp)  
0x00001f4b : mov    %ecx,0x4(%esp)  
0x00001f4f : mov    %eax,-0x2c(%ebp)  
0x00001f52 : call   0x1f6e   
0x00001f57 :    mov    -0x20(%ebp),%ecx  
0x00001f5a :    mov    %eax,-0x30(%ebp)  
0x00001f5d :    mov    %ecx,%eax  
0x00001f5f :    add    $0x2c,%esp  
0x00001f62 :    pop    %esi  
0x00001f63 :    pop    %edi  
0x00001f64 :    pop    %ebx  
0x00001f65 :    pop    %ebp  
0x00001f66 :    ret   

注意main+60位置,它直接将sizeof(i++)得到的数值4放入堆栈,并没有对i自增。
Q5: 为什么形如2["hello"]这样的表达式是什么意思?
#include   
  
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
#define PRINT_CH(charValue)   printf(#charValue" is %c\n", (charValue));  
  
int main(int argc, char **argv)  
{  
    char ch = 2["hello"];  
    PRINT_CH(ch)  
      
    return 0;      
}  


A: 2["hello"]等同于"hello"[2], 又等同于 char *str = "hello";  str[2] .  但是,为什么?
因为编译器对于数组访问根本就采用指针加减的计算方法,即2["hello"]== *(2 + "hello"), "hello"是个字符指针, *(2 + "hello")  == *("hello" + 2), 所以结论也就出来了。
Q6: 为什么一个整形数据用%f格式输出,结果改变了?
           #include   

  
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
#define PRINT_F(floatValue)   printf(#floatValue" is %f\n", (floatValue));  
  
int main()  
{  
    int d = 2;  
    PRINT_F(d)  
      
    return 0;  
}  

A: 原因在于printf分析%f格式时是从对应地址取出sizeof(float)个字节数据然后当成float格式输出,所以,一般情况下,输出都是错误的。
对于float内部的格式,IEEE 754标准写的很清楚,如果出现类似的问题,可以使用如下结构来简单地验证:
           // IEEE 754 single floating number's format(intel little-endian mode)  

typedef union  
{  
    // float value  
    float   f;  
      
    // intel bits mode that stands for float value  
    struct   
    {  
        unsigned int dot     : 23;  // low bits  
        unsigned int exp     : 8;   // middle bits  
        unsigned int sign    : 1;   // highest bit  
    }float_bit;  
}float_value;  


一个简单的测试代码:
           #include   

  
#define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
#define PRINT_F(floatValue)   printf(#floatValue" is %f\n", (floatValue));  
  
// IEEE 754 single floating number's format(intel little-endian mode)  
typedef union  
{  
    // float value  
    float   f;  
      
1.         // intel bits mode that stands for float value  
2.         struct   
3.         {  
4.             unsigned int dot     : 23;  // low bits  
5.             unsigned int exp     : 8;   // middle bits  
6.             unsigned int sign    : 1;   // highest bit  
7.         }float_bit;  
8.     }float_value;  
9.      
10.   
11.  int main(int argc, char **argv)  
12.  {  
13.      float_value fv;  
14.      fv.float_bit.sign = 1;  
15.      fv.float_bit.exp = 128;  
16.      fv.float_bit.dot = 0;  
17.        
18.      PRINT_F(fv.f);  
19.        
20.      return 0;      
21.  }  


输出:
1.     fv.f is -2.000000  



作者:陈曦
日期:2012-8-17 12:53:12
环境:[Mac10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1xcode4.2]  
转载请注明出处




[ 本帖最后由 tiankai001 于 2012-12-15 08:03 编辑 ]
此帖出自单片机论坛
 
 
 

回复

2万

帖子

341

TA的资源

版主

26
 

回复 楼主 tiankai001 的帖子

好东西,,收藏!
此帖出自单片机论坛
 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

27
 
kankan  看看楼主的东东
此帖出自单片机论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表