数组的元素类型决定数组如何定义
如:
元素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(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如果重叠,就应该先从高地址开始复制,这样重叠部分不会影响
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指令。由调用者手动清栈。被调用的函数支持可变参数。调用者根据调用时传入参数的个数,手动平衡堆栈。
两者的相同点与不同点
相同点
不同点
参考: