《C语言内核深度解析》小结

数组

数组的元素类型决定数组如何定义
如:
元素char     数组定义char   a[6];
元素char *   数组定义char *a[6];
元素char *   数组定义char *a[6];

数组元素决定数组指针的类型
如:
元素char     数组定义char   a[6]    数组指针char  (*p)[6]   p = a;
元素char *   数组定义char *a[6]    数组指针char *(*p)[6]   p = a;

所以:不要去在意关于元素类型的指针,只要写好数组就可以了,最后加上元素类型就好了。

数组名 = &数组名

#include 
int main()
{
	int buf[3] = {1, 2, 3};
	printf("0x%x\n", &buf);  //输出0x19ff34
	printf("0x%x\n", buf);   //输出0x19ff34
	return 0;
}

对二维数组的判断,先从高层到低 层,一层一层判断。
如:                                         要记住一条规则,数组名 = 数组首元素的地址
a[2][5];
a        =    &a[0];      //第一层有两个元素
a[0]    =    &a[0][0];  //第二层有五个元素

要判断一个指针是一级指针还是二级指针,只要让指针加一,然后得出它的地址,就知道了。

如:

int (*p)[100]   p + 1 = p的值加上  4 X 100

int a[100] = {0};  a + 1 = a的值加上4

函数指针

函数指针:首先修饰词是函数,主语是指针,
                  类型如:int (* fun)(int);
指针函数:就是返回值为指针的函数,      
                  类型如:int  * fun (int);
函数指针数组:就是元素是函数指针的数组
                  类型如:int (* fun[6])(int);
返回值为指针 的 函数指针数组:就是元素是函数指针的数组
                  类型如:int *(* fun[6])(int);

注意:数组元素类型不能为函数
如                            int fun[6](int);

malloc

malloc(0):不同的编译器可能不同,有可能为NULL;

malloc(4):GCC分配的最低字节是16B,所以回分配16B.

字符串常量放到代码段。

字节对齐

更改C编译器的缺省字节对齐方式

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:

#pragma pack( )   //1字节对齐
#pragma pack(4)  //4字节对齐

另外,还有如下的一种方式:

__attribute__(( aligned (n) )); //结构体n字节对齐
__attribute__(( packed      )); //取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

 

 

 

 

 

常用命令

nm命令用来查看静态库中都有那些符号

#nm libdemo.a
demo.o:
00000000 T func1
00000014 T func2

ldd命令用来查看可执行文件使用了哪些动态库,并且查看这些动态库是否能被加载并被解析。

#ldd test
libdemo.so => no found

 

 

 

 

杂项

变量不能被两种以上的存储类的关键字修饰。

存储类型关键字为:ae2rstv

  • auto 
  • static 
  • register 
  • extern 
  • volatile 
  • restrict 
  • typedef 

错误用法:typedef   static  a;

restrict:用于告诉编译器,对象已经被指针所引用,不能通过除该指针外所有其他直接或间接的方式修改该对象的内容。

int fun1(int *s1, int *s2)
{
	*s1 = 111;
	*s2 = 222;
	return *s1;//不一定会返回111,因为多线程可能会影响
}
int fun2(int * restrict s1, int * restrict s2)
{
	*s1 = 111;
	*s2 = 222;
	return *s1;//肯定返回111
}

 

 

计算结构体中某个元素相对结构体首字节地址的偏移量,其实质就是通过编译器帮我们来计算。

#define offsetof(TYPE, MEMBER) ((int) & ((TYPE *) 0) -> MEMBER)

根据成员地址计算结构体的首地址。

#define offsetof(TYPE,MEMBER)   ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(PTR, TYPE, MEMBER)    ({  \
            const typeof( ((TYPE *)0) -> MEMBER) *__mptr=(PTR);  \//定义该成员的指针
                (TYPE *) ((char *)__mptr - offsetof(TYPE,MEMBER)); })
				
//typeof( ((TYPE *)0) -> MEMBER)  得到该成员的类型

//为什么要用个临时遍历__mptr
//因为不用临时变量,那下面减去offset时就会改变PTR的原有值

为什么是(char *)?

如果是(int *)则减一,地址就减4,用(char *)只会是减1

判断大小端的程序

int fun()
{
    union{
        int   a;
        char  b;
    }n;
    n.a = 1;
    return (n.b == 1);
}

int main()
{

	printf("%d\n", fun());
	return 0;
}

交换两个数

	int a = 1;
	int b = 2;
	a ^= b;
	b ^= a;
	a ^= b;
	printf("%d %d\n", a, b);

做乘法和除法运算时,尽量用位移运算。

//a * 10
a = ((a << 1) + (a << 3));

strcpy,strlen,strcat,strcmp,strchr,memcpy

#include 
char *my_strcpy(char *dest, const char *source)
{
    //if ((dest == NULL) || (source == NULL)) return dest;
    assert( (strDest != NULL) && (strSrc != NULL) ); 
    char *tmp_ch = dest;
    while (*tmp_ch++ = *source++);
    return dest;
}

char *my_strcat(char *dest, const char *source)
{
	if (dest == NULL || source == NULL) return dest;
	char *p = dest;
	while (*p != '\0') p++;
	my_strcpy(p, source);
	return dest;
}

void* mymemcpy( void *dest, const void *src, size_t count )
{
    if (dest == NULL || src == NULL || !count) return dest;
    char*       pdest = static_cast      (dest);
    const char* psrc  = static_cast(src);
    if( pdest > psrc && pdest < psrc+count ) {     //能考虑到这种情况就行了
        for( size_t i = count-1; i!=-1; --i )
                pdest[i] = psrc[i];
    }else{
        for( size_t i = 0; i 0) return  1;
	else if (ret < 0) return -1;
	else              return  0;
}

int strStr(char * haystack, char * needle)
{
    if (haystack == NULL || needle == NULL) return -1;
    if (needle[0] == '\0')                  return 0;
    int i = 0, j = 0;
    while (haystack[i] && needle[j]) {
        if (haystack[i] == needle[j]) ++i, ++j;//如果相当就后移
        else {
            i = i - j + 1;//指向开始字符的下一个字符
            j = 0;//重头比较
        }
    }
    return (needle[j] ? -1 : (i - j));//如果needle没有到结尾就输出 -1
}

int main()
{
	char a[64] = {'a', 'a', '\0'};
	char b[64] = {'b', 'b', '\0'};

	printf("%d\n", my_strcmp(a, b));
	return 0;
}

memcpy如果重叠,就应该先从高地址开始复制,这样重叠部分不会影响

《C语言内核深度解析》小结_第1张图片

strstr参考:https://leetcode-cn.com/problems/implement-strstr/solution/cyu-yan-jian-jian-dan-dan-de-ji-xing-dai-ma-jie-15/

 

GCC编译和链接

gcc -E *.c -o *.i;   //预处理的命令实现
gcc -S *.c -o *.s;   //编译的命令实现
gcc -c *.c -o *.o;   //汇编的命令实现
gcc    *.o -o *.out; //链接的命令实现

#if  和  #if defined  的区别

#if defined(a) && defined(b)
//code
#endif

跳到0x10000上去执行程序

* ( (void (*)() ) 0x10000)();

最好还是定义该函数类型的指针,然后用指针去执行函数。

int fun(int a)
{
    printf("%d --\n", a);
}
int main()
{
    int (*p)(int);
    p = fun;
    (*p)(2);
    p(3);
    return 0;
}

输出:

2 --
3 --

计算数组元素个数的宏

#define ARRAY_SIZE(a) ((sizeof(a)) / (sizeof(a[0])))

位段

参考:https://blog.csdn.net/QQ2558030393/article/details/98089014

 

关于函数调用方式__stdcall和__cdecl详解

__stdcall

__stdcall的全称是standard call。是C++的标准调用方式。

函数参数的入栈顺序为从右到左入栈。函数返回时使用retn x指令,其中x为调整堆栈的字节数。这种方式叫做自动清栈。即被调用的函数的参数个数是固定的,调用者必须严格按照定义传递参数,一个不多,一个不少。

__cdecl

__cdecl的全称是C Declaration,即C语言默认的函数调用方式。

函数参数的入栈顺序为从右到左入栈。函数返回时作用ret指令。由调用者手动清栈。被调用的函数支持可变参数。调用者根据调用时传入参数的个数,手动平衡堆栈。

两者的相同点与不同点

相同点

  • 参数入栈顺序相同:从右到左

不同点

  1. 堆栈平衡方式不同:__stdcall自动清栈,__cdecl手动清栈。
  2. 返回指令不同:_stdcall使用retn x, __cdecl使用ret
  3. 编译后函数的修饰名不同: 假设有函数int foo(int a, int b),
  • 采用__stdcall编译后的函数名为_foo@8(函数名自动加前导下划线,后面紧跟着一个@,其后紧跟着参数的尺寸)
  • 采用__cdecl编译后的函数名为_foo

参考:

  • https://www.jb51.net/article/93489.htm
  • https://www.jb51.net/article/44652.htm
  • https://www.cnblogs.com/SoaringLee/p/10532535.html
  • https://blog.csdn.net/qq_33921804/article/details/52663212

 

 

 

 

 

你可能感兴趣的:(面试)