c语言中一些会遇到的问题

小写字母转换为大写字母的方法就是将小写字母的 ASCII 码值减去 32

1.字符串中末尾是会带有'\0',也会占用一个字节。
2.strlen不会统计'\0'
//c语言中的assert函数
//expression -- 这可以是一个变量或任何 C 表达式。如果 expression 为 TRUE,assert() 不执行任何动作。如果 expression 为 FALSE,assert() 会在标准错误 stderr 上显示错误消息,并中止程序执行。
void assert(int expression);
//标准的strcpy函数
char * strcpy(char * str1,const char * str2)//const跳过char直接修饰str2,不能通过str2修饰str2所指向的值。
{
	char * address=str1;
	while(*str2!='\0')
	{
		assert( (strDest != NULL) && (strSrc != NULL) );//如果里面是true,则不执行,否则报错,终止程序运行
		*str1++=*str2++;
	}
	return address;//方便链式操作
}

//通过子函数开辟内存空间
int GetMemory( char **p, int num )
{
 *p = (char *) malloc( num );
 if(*p==NULL)//判断内存申请是否成功?
 {return 0;}
 else return 1;
}
void Test( void )
{
 char *str = NULL;
 GetMemory( &str, 100 );
 strcpy( str, "hello" ); 
 printf( str );
 free(str);
} 

// 关于if比较,几种不同类型的变量的与0值的判断
 BOOL型变量:if(!var)
 int型变量: if(var==0)
 float型变量:
 const float EPSINON = 0.00001;
 if ((x >= - EPSINON) && (x <= EPSINON)
 指针变量:  if(var==NULL)

//防止宏定义的坑

//关于头文件
#ifndef __INCvxWorksh
#define __INCvxWorksh
#endif 
的作用是防止被重复引用。

//c++重载
 作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为: 
void foo(int x, int y); 
  该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。
  为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。

//memcpy函数
void *memcpy(void *str1, const void *str2, size_t n)
str1 -- 指向用于存储复制内容的目标数组,类型强制转换为 void* 指针。
str2 -- 指向要复制的数据源,类型强制转换为 void* 指针。
n -- 要被复制的字节数。(非常需要注意,这个字节数,一个int 4个字节)
memcpy(a + 3, a, 5*sizeof(int));//正确的拷贝方法

//初始化数组等等,初始值是0 
memset(str, 0, sizeof(str));  //只能写sizeof(str), 不能写sizeof(p)

//volatile关键字 在程序编译的过程中起到作用
一个变量也许会被后台程序改变,关键字 volatile 是与 const 绝对对立的。它指示一个变量也许会被某种方式修改,这种方式按照正常程序流程分析是无法预知的(例如,一个变量也许会被一个中断服务程序所修改)。这个关键字使用下列语法定义:
变量如果加了 volatile 修饰,则会从内存重新装载内容,而不是直接从寄存器拷贝内容。 
volatile应用比较多的场合,在中断服务程序和cpu相关寄存器的定义。

volatile 用于相关寄存器定义(不会被编译器优化)
#define GPC1CON *((volatile unsigned int*)0xE0200080)
GPC1CON 为寄存器名称、0xE0200080 为寄存器地址、(volatile unsigned int*) 为强制类型转换
是作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。
volatile 可以保证对特殊地址的稳定访问。
volatile 指出 i 是随时可能发生变化的,每次使用它的时候必须从 i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在 b 中。而优化做法是,由于编译器发现两次从 i读数据的代码之间的代码没有对 i 进行过操作,它会自动把上次读的数据放在 b 中。而不是重新从 i 里面读。这样以来,如果 i是一个寄存器变量或者表示一个端口数据就容易出错

寄存器地址为什么要加 volatile 修饰呢?
因为,这些寄存器里面的值是随时变化的
由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化
使用领域
1、并行设备的硬件寄存器
2、一个中断服务子程序中会访问到的非自动变量
3、多线程应用中被几个任务共享的变量
一个参数既可以是const还可以是volatile吗,可以,例如只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为 程序不应该试图去修改它。

extern关键字先声明一下num变量,告诉编译器num这个变量是存在的,但是不是在这之前声明的,你到别的地方找找吧,果然,这样就可以顺利通过编译啦。但是你要是想欺骗编译器也是不行的,比如你声明了extern int num;但是在后面却没有真正的给出num变量的声明,那么编译器去别的地方找了,但是没找到还是不行的。(是程序链接阶段才起到作用)

//判断计算机是小端模式还是大端模式
    union UN
    {
        char c[4];
        int i;//共用四个字节,
    }un;
    un.i = 1;
        if (un.c[0] == 1)//通过数组的地地址,来判断机器是大端模式还是小端模式。
            return 0;//小端
        else 
            return 1;//大端


typedef struct tagWaveFormat
{ 
 char cRiffFlag[4]; 
 UIN32 nFileLen; 
 char cWaveFlag[4]; 
 char cFmtFlag[4]; 
 char cTransition[4]; 
 UIN16 nFormatTag ; 
 UIN16 nChannels; 
 UIN16 nSamplesPerSec; 
 UIN32 nAvgBytesperSec; 
 UIN16 nBlockAlign; 
 UIN16 nBitNumPerSample; 
 char cDataFlag[4]; 
 UIN16 nAudioLength; 

} WAVEFORMAT; 
假设WAV文件内容读出后存放在指针buffer开始的内存单元内,则分析文件格式的代码很简单,为:
WAVEFORMAT waveFormat;
memcpy( &waveFormat, buffer,sizeof( WAVEFORMAT ) ); 

//递归法实现1+2+。。。.+n
int addsum(int n)
{
	if(n>0){
	return n+addsum(n-1);
	}
}
gcc编译器
gcc -o 直接output生成可执行程序
    -v 生成很多信息
    -c 只编译源文件,而不进行链接,因此,对于链接中的错误是无法发现的(func_b() 函数并没有定义,所以在链接时会产生错误(编译时不会产生错误))
    -E 生成预处理文件
    -S 生成汇编文件
    -I 手动链接库
    -L 链接库的地址
    -fPIC 生成动态链接库

预处理错误
	include错误
	define
	
include的“”和<>的区别?
“”优先在本工程项目中找头文件,<>默认先在turboc 系统目录中的include文件夹中找头文件系统库。


#error命令是C/C++语言的预处理命令之一,当预处理器预处理到#error命令时将停止编译并输出用户自定义的错误消息。
语法:
#error 用户自定义的错误消息
举例:
#if STDC_VERSION != 199901L
#error Not C99
#endif

关键字static的作用是什么?

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用

  1. 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
  2. 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
  3. 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

const意味着"只读"

int (* arr )[3]; //arr是一个指向包含3个int元素的数组的指针变量
struct Student p_struct; //结构体类型的指针
int(p_func)(int,int); //指向返回类型为int,有2个int形参的函数的指针
int
p_pointer; //指向 一个整形变量指针的指针
//取地址
int num = 97;
int* p_num = #
//
int arr[3] = {1,2,3};
int (*p_arr)[3] = &arr;
//
int add(int a , int b)
{
return a + b;
}
int (*fp_add)(int ,int ) = add;

*p_age = 20; //通过指针修改指向的内存数据

//指针之间的赋值
int* p1 = & num;
int* p3 = p1;

//空指针
//指向空,或者说不指向任何东西。在C语言中,我们让指针变量赋值为NULL表示一个空指针,而C语言中,NULL实质是 ((void*)0) , 在C++中,NULL实质是0。
//换种说法:任何程序数据都不会存储在地址为0的内存块中,它是被操作系统预留的内存块。

//错误
int*p;
*p = 10; //Oops! 不能对一个未知的地址解地址

//void*类型指针 不知道类型的指针
//p->member 等价于 (*p).member

//改变数值
void change(int* pa)
{
(pa)++; //因为传递的是age的地址,因此pa指向内存数据age。当在函数中对指针pa解地址时,
//会直接去内存中找到age这个数据,然后把它增1。
}
int main(void)
{
int age = 19;
change(&age);
printf(“age = %d\n”,age); // age = 20
return 0;
}
//数值交换
//正确的写法:通过指针
void swap_ok(int
pa,int*pb)
{
int t;
t=*pa;
*pa=*pb;
*pb=t;
}
//指针函数
void echo(const char *msg)
{
printf("%s",msg);
}
int main(void)
{
void(p)(const char) = echo; //函数指针变量指向echo这个函数
p("Hello "); //通过函数的指针p调用函数,等价于printf("Hello ")
return 0;
}
//const和指针
int a = 1;

int const *p1 = &a;        //const后面是*p1,实质是数据a,则修饰*p1,通过p1不能修改a的值
const int*p2 =  &a;        //const后面是int类型,则跳过int ,修饰*p2, 效果同上
int* const p3 = NULL;      //const后面是数据p3。也就是指针p3本身是const .
const int* const p4 = &a;  // 通过p4不能改变a 的值,同时p4本身也是 const
int const* const p5 = &a;  //效果同上

下面是volatile变量的几个例子:每次强制从内存中读,而不是寄存器和cache中读。

  1. 并行设备的硬件寄存器(如:状态寄存器)

  2. 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

  3. 多线程应用中被几个任务共享的变量

  4. 一个参数既可以是const还可以是volatile吗?解释为什么。只读状态寄存器。

  5. 一个指针可以是volatile 吗?解释为什么。一个例子是当一个中服务子程序修该一个指 向一个buffer的指针时。

  6. 下面的函数有什么错误:
    int square(volatile int *ptr)
    {
    return *ptr * *ptr;//本来是想着返回这个的平方的,但是volatile修饰可能随时都会被修改,和预期的值可能会有区别。
    }

要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一个较晦涩的方法是:
*(int * const)(0x67a9) = 0xaa55;

在linux下会有两种IO,一种是同步IO另外一种是异步IO,内存映射IO等
预读
预读是指采用预读算法在没有系统的IO请求的时候事先将数据从磁盘中读入到缓存中,然后在系统发出读IO请求的时候,就会实现去检查看看缓存里面是否存在要读取的数据,如果存在(即命中)的话就直接将结果返回,这时候的磁盘不再需要寻址、旋转等待、读取数据这一序列的操作了,这样是能节省很多时间的;如果没有命中则再发出真正的读取磁盘的命令去取所需要的数据。
缓存的命中率跟缓存的大小有很大的关系,理论上是缓存越大的话,所能缓存的数据也就越多,这样命中率也自然越高,当然缓存不可能太大,毕竟成本在那儿呢。如果一个容量很大的存储系统配备了一个很小的读缓存的话,这时候问题会比较大的,因为小缓存缓存的数据量非常小,相比整个存储系统来说比例非常低,这样随机读取(数据库系统的大多数情况)的时候命中率也自然就很低,这样的缓存不但不能提高效率(因为绝大部分读IO都还要读取磁盘),反而会因为每次去匹配缓存而浪费时间。
执行读IO操作是读取数据存在于缓存中的数量与全部要读取数据的比值称为缓存命中率(Read Cache Hit Radio),假设一个存储系统在不使用缓存的情况下随机小IO读取能达到150IOPS,而它的缓存能提供10%的缓存命中率的话,那么实际上它的IOPS可以达到150/(1-10%)=166。
回写
要先说一下,用于回写功能的那部分缓存被称为写缓存(Write Cache)。在一套写缓存打开的存储中,操作系统所发出的一系列写IO命令并不会被挨个的执行,这些写IO的命令会先写入缓存中,然后再一次性的将缓存中的修改推到磁盘中,这就相当于将那些相同的多个IO合并成一个,多个连续操作的小IO合并成一个大的IO,还有就是将多个随机的写IO变成一组连续的写IO,这样就能减少磁盘寻址等操作所消耗的时间,大大的提高磁盘写入的效率。
读缓存虽然对效率提高是很明显的,但是它所带来的问题也比较严重,因为缓存和普通内存一样,掉电以后数据会全部丢失,当操作系统发出的写IO命令写入到缓存中后即被认为是写入成功,而实际上数据是没有被真正写入磁盘的,此时如果掉电,缓存中的数据就会永远的丢失了,这个对应用来说是灾难性的,目前解决这个问题最好的方法就是给缓存配备电池了,保证存储掉电之后缓存数据能如数保存下来。
和读一样,写缓存也存在一个写缓存命中率(Write Cache Hit Radio),不过和读缓存命中情况不一样的是,尽管缓存命中,也不能将实际的IO操作免掉,只是被合并了而已。
控制器缓存和磁盘缓存除了上面的作用之外还承当着其他的作用,比如磁盘缓存有保存IO命令队列的功能,单个的磁盘一次只能处理一个IO命令,但却能接收多个IO命令,这些进入到磁盘而未被处理的命令就保存在缓存中的IO队列中。
RAID(Redundant ArrayOf Inexpensive Disks)
如果你是一位数据库管理员或者经常接触服务器,那对RAID应该很熟悉了,作为最廉价的存储解决方案,RAID早已在服务器存储中得到了普及。在RAID的各个级别中,应当以RAID10和RAID5(不过RAID5已经基本走到头了,RAID6正在崛起中,看看这里了解下原因)应用最广了。下面将就RAID0,RAID1,RAID5,RAID6,RAID10这几种级别的RAID展开说一下磁盘阵列对于磁盘性能的影响,当然在阅读下面的内容之前你必须对各个级别的RAID的结构和工作原理要熟悉才行,这样才不至于满头雾水,推荐查看wikipedia上面的如下条目:RAID,Standard RAID levels,Nested RAID levels。

异步IO和同步IO区别
如果是同步IO,当一个IO操作执行时,应用程序必须等待,直到此IO执行完,相反,异步IO操作在后台运行,
IO操作和应用程序可以同时运行,提高系统性能,提高IO流量; 在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行,而异步文件IO中,
线程发送一个IO请求到内核,然后继续处理其他事情,内核完成IO请求后,将会通知线程IO操作完成了。

大小端模式
int main(void)
{
int a = 0x1234;
char b = ((char)&a);
if (b == 0x12)
printf(“BIGEND\n”);
if (b == 0x34)
printf(“littleEND\n”);
system(“PAUSE”);
return 0;
}

#多线程
int a=10;
子进程会复制一份
多线程会公用
pthread.h头文件
编译要加-lpthread

//创建线程
pthread_create()
main函数退出后,不会等待子线程。
pthread_join 等待线程结束
两个线程运行速度不一致的。
s++,三步操作,读s,s+1,写回s
锁只有一个,一个进程用了锁,那么另外的线程就必须等待别的进程解锁以后,才能加锁,完成相应的代码。
锁机制
pthread_mutex_t writable[100]; //lock
pthread_mutex_init(&writable[i], NULL); //锁的初始化
pthread_mutex_lock(&writable[i]); //加锁
… //临界区
pthread_mutex_unlock(&writable[i]); //解锁

假共享,在多核多线程运算中。

指针和引用的区别
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
(2)可以有const指针,但是没有const引用;
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
(4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
(5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。
(6)"sizeof引用"得到的是所指向的变量(对象)的大小,而"sizeof指针"得到的是指针本身的大小;
(7)指针和引用的自增(++)运算意义不一样;引用是值++;比如b是引用a[0]的,++表示a[0]的值++从0变为1;

野指针出现的情况:
1.指针未初始化
指针变量在定义时不会自动初始化成空指针,而是随机的一个值,可能指向任意空间,这就使得该指针成为野指针。因此指针在初始化时要么指向一个合理的地址,要么初始化为NULL。
2.指针指向的变量被free或delete后没有置为NULL
在调用free或delete释放空间后,指针指向的内容被销毁,空间被释放,但是指针的值并未改变,仍然指向这块内存,这就使得该指针成为野指针。因此在调用free或 delete之后,应将该指针置为NULL。
3.指针操作超过所指向变量的生存期
当指针指向的变量的声明周期已经结束时,如果指针仍然指向这块空间,就会使得该指针成为野指针。这种错误很难防范,只有养成良好的编程习惯,才能避免这类情况发生。
注意:野指针只能避免而无法判断
无法判断一个指针是否为野指针,因为野指针本身有值,指向某个内存空间,只是这个值是随机的或错误的。而空指针具有特殊性和确定性,可以进行判断,因此要避免在程序中出现野指针。
危害:
1、指向不可访问的地址
危害:触发段错误。
2、指向一个可用的,但是没有明确意义的空间
危害:程序可以正确运行,但通常这种情况下,我们就会认为我们的程序是正确的没有问题的,然而事实上就是有问题存在,所以这样就掩盖了我们程序上的错误。
3、指向一个可用的,而且正在被使用的空间
危害:如果我们对这样一个指针进行解引用,对其所指向的空间内容进行了修改,但是实际上这块空间正在被使用,那么这个时候变量的内容突然被改变,当然就会对程序的运行产生影响,因为我们所使用的变量已经不是我们所想要使用的那个值了。通常这样的程序都会崩溃,或者数据被损坏。

什么原因导致的内存泄露的问题呢?
1.分配完内存之后忘了回收;
2.程序Code有问题,造成没有办法回收;
3.某些API函数操作不正确,造成内存泄漏。

定位错误:
遇到OOM,你首先要确定他是由于什么原因引起的?是因为堆空间设置太小引起还是因为内存泄露引起。实际上,内存泄露的问题可以通过增大堆空间暂时得到解决,但是他不是长久之计。
mtrace

#include <stdio.h>
 
int main()
{
        setenv("MALLOC_TRACE", "taoge.log", "1");
        mtrace();
 
        int *p = (int *)malloc(2 * sizeof(int));
 
        return 0;
}

gcc -g test.c,运行可定位内存溢出的位置。

发布了27 篇原创文章 · 获赞 20 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_33479881/article/details/97376982