C语言深度解剖----重点部分笔记

第一章:关键字

auto:所有变量在编译器默认缺省情况下都是auto的
register:请求编译器尽可能的将变量存在CPU内部寄存器而不是通过内存寻址访问以提高效率。尽可能是因为一个CPU的寄存器也就那么几个或几十个,如果定义了很多register变量是不可能全部放入的。
用&符号是去内存中取址,取不到放在编译器中的地址。CPU不直接和内存打交道而是通过寄存器去进行交流。因此&符号不能获得register的值。
static(调试版):

#include<iostream>
using namespace std;

static int j;

void fun1(void)
{   
    static int i = 0;        
    i++;   
}

void fun2(void)
{   
    j = 0;      
    j++;   
}
int main()
{
    for (int k = 0; k < 10; k++)
    {  
        fun1();  
        fun2();         
    }
    system("pause");
}
//结果:
i=1,2,3,4,5,6,7,8,9,10
j=1,1,1,1,1,1,1,1,1,1

其实主要是考察调试的问题,fun1(),fun2()设断点然后F5,再F10,F11的问题。

static(输出版):

#include<iostream>
using namespace std;
static int j;
int* fun1(void)
{
    static int i = 0;
    int *a = &i;//这样做可以获得i的地址,也可以*i获得i的值
    i++;
    return a;
}
int* fun2(void)
{
    j = 0;
    int *a = &j;
    j++;
    return a;
}
int main()
{
    for (int k = 0; k < 10; k++)
    {
        fun1();
        fun2();
        cout << *b << endl;
        cout << *c << endl;
    }
    system("pause");
}

sizeof://是关键字不是函数

int i=0;
sizeof(int)==sizeof(i)==sizeof i==4

double a[100] = {0.0};
double *p=a;
sizeof(a);//值为800
sizeof(p);//值为4
sizeof(a[0])==sizeof(*a)//值为8
sizeof(&a)==sizeof(&a[0])//值为4,有取址符号的都为4,因为地址的sizeof为4

长循环放在内层可以节省效率,减少CPU切换循环层的次数。

符号表是一种用于语言翻译器的数据结构,在符号表中每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型,作用于以及内存地址。const由于没有了存储和读内存的操作,使得它的效率也很高。
0x01<<2+3;//由于+号的优先级大于<<或>>符号,因此结果为0x01左移5位,值为32,一个整形长度位32位,左移或右移总位数不能超过32位。

 

第四章:指针与数组

*p.f=>*(p.f)//因为.的优先级高于*,因此该句的语义为对p取f偏移作为指针,然后指针去解引用。解引用就是取值的意思。
int *ap[]=>int *(ap[])=>(int *)(ap[])//因为[]的优先级高于*,因此ap是一个元素为int指针的数组。
int (*ap)[]//就是指向数组的指针。
int *fp()=>int *(fp())//因为()的优先级高于*,因此fp是一个函数,返回int *类型,也就是返回指针的函数。函数指针指向函数的入口地址。
int (*fp)()//fp为一个指向函数的指针,也就是函数的指针,这里函数参数为空,返回值为int。

++和--的优先级高于*

练习:
1.int (*(*d)[5])(int *);
*d是一个指针,指针指向一个含有5个元素的数组,每一个元素是一个函数指针,函数参数为int*,返回值为int。

2.int (*(*e)(int*))[5];
*e是一个指针,这个指针是一个函数指针,形参为int*,返回一个指向数组的指针,这个指针是整型的。
我们需要改为:
typedef int (*pArr)[5];//这里的这个括号说明这是一个函数指针数组,int为返回值。如果没有括号这是就是别名的意思。
typedef pArr (*func)(int*);

char a[5] = { 'A', 'B', 'C', 'D', 'E' };
char(*p3)[5] = &a;//这里因为左右相等的原则,只能以char(*p3)[5]的形式,其他都不可以,这里的p3是指整个数组,所以*(p3+1)指的是E后面的那个字符,也就是无效空字符。
#include<stdio.h>
int main()
{
    char a[5] = { 'A', 'B', 'C', 'D', 'E' };
    char(*p3)[5] = &a;
    //char(*p4)[5] = a;
    char *p4=*(p3 + 1);
    return 0;
}


int *p=NULL;

int *p;
*p=NULL;//这个可能会不安全,因为p是一个未知地址,比较好的方法是

int i=10;
int *p=&i;
*p=NULL;

如何给一个地址存入值
int *p=(int *)0x12ff7c;//如何判断一个内存是否可访问:int i=0;去到内存中取&i的值即可。但其实这种写法在VS上会报错,最好还是别用这种方法,说是因为调试器会保护要写区域的内存不允许访问。
*p=0x100;//将值100存入0x12ff7c
或者直接:*(int *)0x12ff7c=0x100;

#include <iostream>
using namespace std;

struct Test
{
    int Num;
    char *pcName;
    short sDate;
    char ch[2];
    short str[4];
}*p;//值为20,转为16进制位14

void main()
{
        p = (Test*)0x100000;

        printf("p+0x01  ->  0x%08x\n", p+0x01);//+0x01等价于加了一整个数组结构,值为0x100014
        printf("(unsigned long)p+0x01  ->  0x%08x\n", (unsigned long)p+0x01);//只加一,0x100001
        printf("(unsigned int*)p+0x01  ->  0x%08x\n", (unsigned int*)p+0x01);//加了四,0x100004
    system("pause");
}

大端模式
       a[0]                a[1]              a[2]                   a[3]  
        1                   2                  3                      4
0x00 0x00 0x00 0x01    0x00 0x00 0x00 0x02    0x00 0x00 0x00 0x03    0x00 0x00 0x00 0x04 
大端模式读出来的值是0x00000100,也就是0x100(大端从左往右读)

在32位的x86系统下,是小端模式——认为第一个字节是最低位字节
在内存中存放为:
a[0]                     a[1]
0x01 00 00 00     0x02 00 00 00
所以读取为:0x02000000(小端从右往左读)

二维数组的定义为

a=>&a[0]
a[i]=>&a[0]+i*sizeof(char)*4
a[i][j]=>&a[0]+i*sizeof(char)*4+j*sizeof(char)=>*(*(a+i)+j)
#include<stdio.h>
#include<iostream>
using namespace std;
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };//因为是小括号,所以a的值为a={1,3,5};
    //int a[3][2]={{0,1},{2,3},{4,5}};这样才是正确的取值
    int *p;
    p = a[0];//a[0]的值是{1,3}
    printf("%d", p[0]);//p[0]的值为1
    system("pause");
}

算内存最好的方法是画内存布局图

#include<stdio.h>
int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;/*这里如果没有疾病的话是有问题的...报错在于无法将int(*)[5]类型分配给int(*)[4],除非改为int (*p)[5],但这样就体现不出意义了,编译器为VS2013,可能是编译器的问题。不同的编译器的起始
值不一定为0。后面有提到应该把指针初始定为NULL,就是把它拴在尺子的0mm处,估计是因为没有拴指针导致的问题,看下之后第五章有没有解决办法。*/
    printf("a_ptr=%#p,p_ptr=%#p\n", &a[4][2], &p[4][2]);
    printf("%p,%d", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}


但其实这里最妙的地方在于int(*p)[4]的含义,它是指int[][4],这里的p++是以4个单位一起加的,结果为a[1][0],但如果int *p这里的p++就是一个一个加的,也就是a[0][1]。

#include<stdio.h>
int main()
{
    int a[3][4];
    int(*p)[4];//int *p;p=a;这玩意也跑不了啊,总而言之思想记住就好。
    p = a;//如果这样跑的话这里就ok
    printf("a_ptr=%#p,p_ptr=%#p\n", &a[4][2], &p[4][2]);
    printf("%p,%d", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

无法向函数传递一个数组:
无法向函数传递指针变量本身,而是指针变量本身的拷贝值
这样问题就来了,当我需要写一个GetMemory的函数,这个函数负责向内存申请所需的空间。

#include<iostream>
using namespace std;
char *GetMemory(char *p, int num)
{
    p = (char*)malloc(num*sizeof(char));
    return p;
}
int main()
{
    char*str = NULL;
    str=GetMemory(str, 10);
/*如果以函数void形式不加return返回值则无效,因为指针本身不会被传递到函数中,而是以_str的拷贝形式传进去的,然后这个_str在函数结束后又释放掉了,因此str还是老样子为NULL,而_str又在堆里自动销毁了。
唯一的用处是以指针指向_str的地址,将其拷贝出来并返回,这样main里才可以查找到这个值。也就是对于要传指针的函数一定要将这个指针传出,否则无法运用到这个指针。*/
    strcpy_s(str,6, "hello");
    cout << str;
    free(str);
    system("pause");
}
#include<iostream>
using namespace std;
char **GetMemory(char **p, int num)
{
    *p = (char*)malloc(num*sizeof(char));
    return p;
}
int main()
{
    char*str = NULL;
    GetMemory(&str, 10);//这里真真正正传的是str的值,而不是通过_str传的
    strcpy_s(str,6, "hello");
    cout << str;
    free(str);
    system("pause");
}

char a[3][4]等效的指针参数为char (*p)[10]
char *a[5]等效的指针参数为char **p
一个中括号可以抵掉一个*号


函数指针

char *fun(char* p1,char* p2){}
main:
char *(*pf)(char* p1,char* p2);
pf=&fun;
*pf("aa","bb");
#include<stdio.h>
void Function()
{
    printf("Call Function!\n");
}
int main()
{
    void (*p)();
    *(int*)&p=(int)Function;//强制将Function从void型转化为int型
    (*p)();//这句话相当于调用了函数Function
    return 0;
}

函数指针的主要作用在于
1.实现面向对象编程中的多态性
ps:很好的代码https://blog.csdn.net/philip_puma/article/details/25973139
2.回调函数

typedef void (*event_handler) (unsigned int para1, unsigned int para2);

struct event {
  unsigned int ev_id;
  event_handler handler;
};
struct event event_queue[MAX_EVENT_SIZE];

这里要搞懂怕是要去看源码了,这种坑不急着开。。。水平还不够。。。
像事件epoll源码里肯定是有的,红黑树那一块会用到。
/*class A{void fun();};
class B{void fun();};
list<EventHandler> _lstEventHandlers;
A a1,a2;B b;
_lstEventHandlers.push_back(a1.fun);
_lstEventHandlers.push_back(a2.fun);
_lstEventHandlers.push_back(b.fun);*/

这个程序是一个事件handle函数,即回调函数,在事件队列中都是用一个函数指针来保存的,程序可以通过扫
描这个事件队列来获取每个事件对应的处理函数,然后调用它。
成员变量这里再加一条:

#include<iostream>
using namespace std;

class A{
public:
    int x;
};

typedef int A::* MemberPointer;

int main(int argc, char *argv[])
{   
    MemberPointer pV;//成员变量指针的定义,等价于int A::* pV;    
    pV = &A::x;
    A a;
    a.*pV = 1;//等价于a.x=1;
    cout << &a->*pV << endl;
//这几种方式都是等价的
    system("pause");
}
#include<stdio.h>
#include<string.h>
char* fun1(char* p)
{
    printf("%s\n",p);
    return p;
}
char* fun2(char* p)
{
    printf("%s\n",p);
    return p;
}
char* fun3(char* p)
{
    printf("%s\n",p);
    return p;
}
int main()
{
    char* (*a[3])(char* p);
    char* (*(*pf[3]))(char* p);
    pf=&a;
    a[0]=fun1;
    a[1]=&fun2;
    a[2]=&fun3;
    pf[0][0]("fun1");//等价于(*pf)[0]("fun1");
    pf[0][1]("fun2");//等价于(*pf)[1]("fun2");
    pf[0][2]("fun3");//等价于(*pf)[2]("fun3");
    return 0;
}

 

 

第五章:内存管理

栈的效率要比堆的效率高,因为栈是编译时分配空间的,而堆是运行时动态分配空间的,且cpu有专门的寄存器(esp,ebp)来操作栈,而堆都是使用间接寻址。栈的存取速度仅次于寄存器,并且栈内可以数据共享。
struct的内存对齐是按照当前结构体中最大的数据的字节对齐的。有double或64位机子上有long,都是按8字节对齐的。
struct结构体只需要对指针进行内存分配,只要结构体内有指针就需要进行内存分配。
举例:

zclReportCmd_t *reportCmd;
typedef struct
{
    uint8 numAttr;
    zclReport_t attrList[];
}zclReportCmd_t;
typedef struct
{
    uint16 attrID;
    uint8 dataType;
    uint8 *attrData;
}zclReport_t;
reportCmd=(zclReportCmd_t *)osal_msg_allocate(sizeof(zclReportCmd_t)+sizeof(zclRepor_t));
reportCmd->attrList[0].attrData=(uint8*)osal_msg_allocate(len);

再来一个:

struct image
{
    struct header *info;
    unsigned char **data;
};
struct image *newimage(int nr,int nc)
{
    struct image *x;
    x=(struct image *)malloc(sizeof(struct image));//image的指针
    x->info=(struct header *)malloc(sizeof(struct header));//info指针
    x->data=(unsigned char **)malloc(sizeof(unsigned char *)*nr);//data的第一层指针
    x->data[0]=(unsigned char *)malloc(nr*nc);//data的第二层指针
    for(i=0;i<nr;i++)
    {
        x->data[i]=(x->data+nr*i);
    }
    return x;
} 


为char*分配指针内存
char *p1="abcdefg";
char *p2=(char*)malloc(sizeof(char)*strlen(p1)+1*sizeof(char));//这里需要考虑到'\0'的内存空间.
//并且字符串常量需要以\0结束,例如char a[7]={'a','b','c','d','e','f','g'};这个就是未结束,需要多加\0。
//不要因为sizeof(char)为1就省略这个写法,这样只会使代码可以执行下降。
strcpy(p2,p1);//但其实strcpy这个函数已经会报错了,需要改为strcpy_s(p2,8,p1);

初始化很重要,不确定的话就设为0或者设为NULL。

内存越界数组加一减一问题,以及内存泄漏的问题。会产生泄漏的内存是堆上的内存(还有资源或句柄等的泄漏情况),也就是由malloc系列函数或者new操作符分配的函数。如果使用完后没有及时free或delete,这块内存就无法释放,直到整个程序终止。
malloc的函数原型是(void*)malloc(int size);由于malloc会有不成功的可能性,因此一定要加入if(NULL!=p)的语句验证。

char *p=(char*)malloc(int size);
free(p);
p=NULL;//一定要将释放的指针设为空,不然就会变成悬垂指针。

 

 

第六章:函数

汇编语言代码往往就是从入口处开始一条一条执行,直到遇到跳转指令比如ARM的B、BL、BX、BLX。
编写strlen函数,用递归的方式解决:
递归1.0

int my_strlen(const char* strDest)
{
    assert(NULL!=strDest);
    if('\0'==*strDest)
    {
        return 0;
    }
    else
    {
        return(1+my_strlen(++strDest));
    }
}

递归2.0

int my_strlen(const char* strDest)
{
    assert(NULL!=strDest);
    return ('\0'!=*strDest)?(1+my_strlen(strDest+1)):0;    
}

递归的效率低是因为每次的函数调用的开销比循环来说大得多,而且递归深度太大可能会出现栈溢出的错误。
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值