C之谜题(持续更新)

C 谜 零碎(逐步更新)

  • 使用gcc -o a a.c -DNDEBUG 取消assert.h中的assert函数的作用
  • C语言转义字符 预备知识: \ddd ddd表示1~3个八进制数字,这个转义符表示的字符就是给定的八进制数值所代表的 字符。 \xddd 与上面的类似,就是八进制数换成了十六进制数 那么问题来了: \40, \100, \x40, \x100, \0123, \x0123的值分别是多少 ? \ddd:代表的是八进制数,将其转换为十进制数再查找ascll表,就会得到正确结果。 \xddd:代表的是十六进制书,转换同上。 \40直接转换。 我们接着看\x100,printf(“\x100\n”);会输出什么呢? 我们先看看它的形式是十六进制形式,我们先将其转换为二进制,则是十二位二进制数0001 0000 0000,超出了一个ascll码的表示范围,将高位舍去,只表示能表示出的范围,即后八位,则ascll码值为000:null。 \100是一样的,舍弃不能表示的位。 同理,对于\x0123,二进制数为0000 0001 0010 0011,舍弃高八位,只表示低八位,ascll码为35,即为 # 。 而\0123是将其看成\012和3,所以打印结果为\n3,即换行和3.
  • 对于移位操作,注意下述问题(暂未找到合适的解释)
    #include<stdio.h>
    int main(int argc, char *argv[])
    {
        int lval=0xFEDCBA98;
        int rval=0xFEDCBA98;
        printf("%x\n",lval<<32);
        printf("%x\n",lval<<-24);
        printf("%x\n",rval>>36);
        printf("%x\n",0xFEDCBA98>>36);
        printf("%x\n",2>>36);
        printf("%x\n",2<<2);
        return 0;
    }
    

    的输出为

    fedcba98
    dcba9800
    ffedcba9
    300
    400
    8
    

    说明变量的移位是按照模运算做的,但是常量的移位在范围外的值很是奇怪。此外对一 个负数,也要注意,lval<<-24相当于左移8位。

  • 在下面的语句中,认为a和x被赋予相同的值得说法是不正确的:
    a = x = y + 3;
    

    如果a是一个字符型变量,那么y+3的值就会被截去一段,以便容纳于字符类型的变量中。 那么a所赋的值就是这个被截断后的值。

  • stdlib.h 定义了EXIT_SUCCESS和EXIT_FAILURE
  • 在C语言中,如果试图在一段代码的首尾分别加上/*和*/来注释掉这段代码,你可能不一定如愿。如果这段代码内部原先就有注释存在,这样就会出现问题,要从逻辑上删除一段C代码,更好的方法是使用#if指令,在#if和#endif之间的程序段就可以有效的从程序中去除,即使这段代码之间原先存在注释也无妨,所以这是一种更为安全的方法。预处理指令的作用远比你想象的更大。
    #if 0
         statements
    #endif
    
  • feof和EOF EOF是End Of File 的缩写,是c语言中标准库中定义的宏,定义为:#define EOF (-1); feof() 用于测试流文件的结束,有宏和函数两种定义: 宏定义: #define feof(_stream) ((_stream)->_flag & _IOEOF),其中_IOEOF的为:#define _IOEOF 0x0010 函数定义:int feof( FILE *stream ); EOF的值为-1,是int类型数据,在32位系统中,可以表示为0xFFFFFFFF; EOF不是一个字符,也不是文件中实际存在的内容。EOF不但能表示读文件到了结尾这一状态,它还能表示 I/O 操作中的读、写错误(可以用 ferror() 来检测)以及其它一些关联操作的错误状态;

    feof()只用于测试流文件的结束,当到达结尾时,返回非0;当文件内部位置指针指向文件结束时,并未立即置位FILE结构中的文件结束标记,只有再执行一次读文件操作,才会置位结束标志,此后调用feof才会返回为真。

    下面这段程序对文本文件和二进制文件都可以:

    int c;
    while((c=fgetc(fp)) != EOF)
    {
        printf("%X/n", c);
    }
    

    如果读到了FF,由于c定义为int型,所以实际上c=0x000000FF,不等于EOF(-1=0xFFFFFFFF),因此不会误判为文件结尾。

    但是如果把c定义为char类型,就有可能产生混淆了。

    char c;
    while((c=fgetc(fp)) != EOF)
    {
        printf("%X/n", c);
    }
    

    因为文本文件中存储的是ASCII码,而ASCII码中FF代表空值(blank),一般不使用,所以如果读文件返回了FF,说明已经到了文本文件的结尾。但是如果是二进制文件,其中可能会包含FF,因此不能把读到EOF作为文件结束的条件,此时只能用feof()函数。

    在VC里,只有当文件位置指针(fp->_ptr)到了文件末尾,然后再发生读/写操作时,标志位(fp->_flag)才会被置为含有_IOEOF。然后再调用feof(),才会得到文件结束的信息。

    因此,如果运行如下程序:

    char c;
    while(!feof(fp))
    {
        c = fgetc(fp);
        printf("%X/n", c);
    }
    

    会发现多输出了一个FF,原因就是在读完最后一个字符后,fp->flag仍然没有被置为_IOEOF,因而feof()仍然没有探测到文件结尾。直到再次调用fgetc()执行读操作,feof()才能探测到文件结尾。这样就多输出了一个-1(即FF)。

    正确的写法应该是:

    char c;
    c = fgetc(fp);
    while(!feof(fp))
    {
        printf("%X/n", c);
        c = fgetc(fp);
    }
    
  • sizeof操作符判断它的操作数的类型长度,以字节为单位表示。操作数既可以是个表达式 (常常是单个变量),也可以是两边加上括号的类型名。下面是两个例子:
    sizeof (int)
    sizeof x
    

    第一个表达式返回整形变量的字节数,其结果自然取决于你所使用的环境。第二个表达式 返回变量x所占据的字节数。注意,从定义上说,字符变量的长度为1个字节。当sizeof的 操作数是个数组名时,它返回该数组的长度,以字节为单位。在表达式的操作数两边加上 括号也是合法的,如下所示:

    sizeof (x)
    

    这是因为括号在表达式中总是合法的。判断表达式的长度并不需要对表达式进行求值,所 以sizeof(a=b+1)并没有向a赋任何值(sizeof可以对一个表达式求值,编译器根据表达式 的最终结果类型来确定大小,一般不会对表达式进行计算)。

    sizeof(2);//2的类型为int,所以等价于sizeof(int);
    sizeof(2+3.14);//3.14的类型为double,2也会被提升成double类型,所以等价于sizeof(double);
    

    sizeof也可以对一个函数调用求值,其结果是函数返回类型的大小,函数并不会被调用, 我们来看一个完整的例子:

    char foo()
    {
        printf("foo()hasbeencalled.\n");
        return 'a';
    }
    int main()
    {
        size_tsz=sizeof(foo());
    //foo()的返回值类型为char,所以sz=sizeof(char),foo()并不会被调用
        printf("sizeof(foo())=%d\n",sz);
    }
    

    void foo3(char a3\[3\]) { int c3=sizeof(a3);//c3== } void foo4(char a4[]) { int c4=sizeof(a4);//c4== } 也许当你试图回答c4的值时已经意识到c3答错了,是的,c3!=3。这里函数参数a3已不再 是数组类型,而是蜕变成指针,相当于char* a3,为什么仔细想想就不难明白,我们调用 函数foo3时,程序会在栈上分配一个大小为3的数组吗不会!数组是“传址”的,调用者只 需将实参的地址传递过去,所以a3自然为指针类型(char*),c3的值也就为4。

    “空结构体”(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取 地址、两个不同的“空结构体”变量又如何得以区分呢于是,“空结构体”变量也得被存储, 这样编译器也就只能为其分配一个字节的空间用于占位了。

    结构体在内存组织上是顺序式的,联合体则是重叠式,各成员共享一段内存,所以整个 联合体的sizeof也就是每个成员sizeof的最大值。结构体的成员也可以是复合类型, 这里,复合类型成员是被作为整体考虑的。

    所以,下面例子中,U的sizeof值等于sizeof(s)。

    union U
    {
    int i;
    char c;
    struct S1 s;
    };
    
  • sizeof 与strlen的区别 strlen(char*)函数求的是字符串的实际长度,直到遇到第一个'\0',然后就返回计数值,且不包括'\0'。
    char aa[10];cout<<strlen(aa)<<endl; //结果是不定的,因为未初始化,'\0'在内存中的位置不确定
    char aa[10]={'\0'}; cout<<strlen(aa)<<endl; //结果为0
    char aa[10]="jun"; cout<<strlen(aa)<<endl; //结果为3
    

    而sizeof()函数返回的是变量声明后所占的内存数,不是实际长度。

    sizeof(aa) 返回10 int a[10]; sizeof(a) 返回40
    
    1.sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。
    该类型保证能容纳实现所建立的最大对象的字节大小。
    2.sizeof是算符,strlen是函数。
    3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。
    sizeof还可以用函数做参数,比如:
    short f();
    printf("%d\n",sizeof(f()));
    输出的结果是sizeof(short),即2。
    4.数组做sizeof的参数不退化,传递给strlen就退化为指针了。
    5.大部分编译程序在编译的时候就把sizeof计算过了是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因
    charstr[20]="0123456789";
    int a=strlen(str);//a=10;
    int b=sizeof(str);//而b=20;
    6.strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度,不是类型占内存的大小。
    7.sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。
    8.当适用于一个结构类型时或变量, sizeof 返回实际的大小,当适用于静态的空间数组, sizeof 归还全部数组的尺寸。
    sizeof 操作符不能返回被动态分派的数组或外部数组的尺寸
    9.数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,
    如:
    fun(char[8]) fun(char[])
    都等价于 fun(char *)
    常在用到 sizeof 和 strlen 的时候,通常是计算字符串数组的长度,如果是对指针,结果则会不一样的:
      char* str = "abacd";
      sizeof(str) //结果 4 --->str是指向字符串常量的字符指针,sizeof 获得的是一个指针所占的空间,应该是长整型的,所以是4;
      sizeof(*str) //结果 1 --->*str是第一个字符,其实就是字符串的第一位'a' 所占的内存空间,是char类型的,占了 1 位;
      strlen(str)= 5 //--->若要获得这个字符串的长度,则一定要使用 strlen
    
    char str[20]="0123456789";
    int a=strlen(str);//a=10;
    int b=sizeof(str);//而b=20;
    char ss[]="0123456789";
    sizeof(ss)结果11===》ss是数组,计算到\0位置,因此是10+1
    sizeof(*ss)结果1===》*ss是第一个字符
    char ss[100]="0123456789";
    sizeof(ss)结果是100===》ss表示在内存中的大小100×1
    strlen(ss)结果是10===》strlen是个函数内部实现是用一个循环计算到\0为止之前
    int ss[100]="0123456789";
    sizeof(ss)结果400===》ss表示在内存中的大小100×4
    strlen(ss)错误===》strlen的参数只能是char*且必须是以'\0'结尾的
    char q[]="abc";
    char p[]="a\n";
    sizeof(q),sizeof(p),strlen(q),strlen(p);===>4 3 3 2
    其实理解 sizeof 只需要抓住一个要点:栈
    程序存储分布有三个区域:栈、静态和动态。能够从代码直接操作的对象,包括任何类型的变量、指针,都是在栈上的;动态和静态存储区是靠栈上的所有指针间接操作的。 sizeof 操作符,计算的是对象在栈上的投影体积;记住这个就很多东西都很清楚了。
    char const* static_string="Hello";
    //sizeof(static_string)是sizeof一个指针,所以在32bitsystem是4
    char stack_string[]="Hello";
    //sizeof(stack_string)是sizeof一个数组,所以是6*sizeof(char)
    char* string=new char[6];
    strncpy(string,"Hello",6");
    //sizeof(string)是sizeof一个指针,所以还是4。
    //和第一个不同的是,这个指针指向了动态存储区而不是静态存储区。
    不管指针指向的内容在什么地方,sizeof 得到的都是指针的栈大小
    C++ 中对引用的处理比较特殊;sizeof 一个引用得到的结果是 sizeof 一个被引用的对象的大小;所以
    struct test
    {
    int a,b,c,d,e,f,g,h;
    };
    int main()
    {
    test &r= new test();
    cout<<sizeof(test)<<endl;//32
    cout<<sizeof r<<endl;//也是32
    }
    r 引用的是整个的 test 对象而不是指向 test 的指针,所以 sizeof r 的结果和 sizeof test 完全相同。
    
  • C的整形算术运算总是至少以缺省整形类型的精度来进行的。为了获得这个精度,表达式 中的字符型和短整型操作数在使用之前被转换为普通整形,这种转换称为整形提升。 例如,在下面表达式的求值中,
    char a,b,c;
    a = b + c;
    

    b和c的值被提升为普通整型,然后再执行加法运算。加法运算的结果将被截断,然后再存 储于a中。

  • for语句,这个要特别注意,曾经犯过的错,
    for (expression1;expression2;expression3)
           statement;
    

    其中expression1为初始化部分,它只在循环开始时执行一次。expression2称为条件部分, 它在循环体每次执行前都要执行一次,就像while语句中的表达式一样。expression3称为 调整部分,它在循环体每次执行完毕, 在条件部分即将执行之前执行。 找出下列的不同,

    unsigned int reverse_bits(unsigned int value)
    {
        unsigned int result=0;
        for(int i=0;i<sizeof(value)*8;++i)
        {
            result=(result<<1)|(value&1);
            value>>=1;
        }
        return result;
    }
    unsigned int reverse_bits2(unsigned int value)
    {
        unsigned int result=0;
        for(int i=0;i<sizeof(value)*8-1;++i,result<<=1,value>>=1)
        {
            result|=(value&1);
        }
        return result;
    }
    
  • dup2
    if((fd = open("/dev/null", O_RDWR, 0)) != -1) {
           (void)dup2(fd, STDIN_FILENO);
           (void)dup2(fd, STDOUT_FILENO);
           (void)dup2(fd, STDERR_FILENO);
           if (fd > STDERR_FILENO)
               (void)close(fd);
       }
    

    dup2(fd, STDIN_FILENO) 在STDIN_FILENO上打开fd所指向的文件,若STDIN_FILENO原来 是打开的则首先先关闭那个这实际上实现了重定向,但是并没有关闭fd,所以如果一开始 环境为程序打开了0,1,2,那么fd最起码是3,因此有必要关它一下

  • ./a.out > outfile 2>&1和./a.out 2>&1 > outfile 的区别 因为shell从左到右处理命令行,所以
    ./a.out > outfile 2>&1
    

    首先设置标准输出到outfile,然后执行dup将标准输出复制到描述符2上,其结果是将标 准输出和标准错误设置为同一个文件,即描述符1和描述符2指向同一个文件表项。而对于 命令行

    ./a.out 2>&1 outfile
    

    由于首先执行dup,所以使描述符2称为终端(假设命令是交互运行的),标准输出重定向到 outfile。结果是描述符1指向outfile的文件表项,描述符2指向终端的文件表项。

  • 如果使用添加标志打开一个文件以便读、写,能否仍用lseek在任一位置开始读?能否用 lseek更新文件中的任一部分的数据?

    这种情况下,仍然可以使用lseek和read函数读文件中任意一处的内容,但是write函数在 写数据之前会自动将文件偏移量设置为文件尾,所以写文件时只能从文件尾开始。

  • restrict关键字

    restrict是c99标准引入的,它只可以用于限定和约束指针,并表明指针是访问一个数据 对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都 必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;这样做的好处是, 能帮助编译器进行更好的优化代码,生成更有效率的汇编代码.如 int *restrict ptr, ptr指向的内存单元只能被 ptr 访问到,任何同样指向这个内存单元的其他指针都是未定 义的,直白点就是无效指针。restrict的出现是因为C语言本身固有的缺陷,C程序员应当主动地规避这个缺陷,而编译器也会很配合地优化你的代码.

  • #ifdef 和 #if defined区别

    注意两者都有个define的作用,区别在于使用方式上。前者的通常用法是:

    #ifdef  XXX
    
       ....
    
    #else
    
       ....
    
    #endif
    
        只能在两者中选择是否有定义。对于后者,常用法是:
    
    #if defined xxx1
    
       ....
    
    #elif defined xxx2
    
       ....
    
    #elif defined xxx3
    
       ....
    
    #endif
    
         可以在多个中选择是否有定义.
    
  • 在C语言中,注释不允许嵌套,在下面的代码中就会存在问题:
    void
    squares( int limit )
    {
        /* comment out this entire function
           int i;/* loop counter */  《-------i的定义没了
        /*
        **print table of squares
        */
        for (i = 0; i < limit; i+=1 )
            printf("%d %d0");
        end of commented-out code */  《-------这里并没有被注释!!!!"*/"是错误的
    }
    

Date: 2014-10-31T16:38+0800

Author: kirchhoff

Org version 7.9.3f with Emacs version 24

Validate XHTML 1.0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值