`

C语言,你真的弄懂了么?

阅读更多

程序(来源 ):

 

#include <stdio.h>

int main(void) {
    int x[4];
    printf("%p\n", (void*) (x));
    printf("%p\n", (void*) (x + 1));
    printf("%p\n", (void*) (&x));
    printf("%p\n", (void*) (&x + 1));
}

 假设x的地址为n,那么输出为:

 

n
n+4
n
n+16

 window下使用gcc编译输出结果:

 

0x22cd44
0x22cd48
0x22cd44
0x22cd54

 前三个还比较好理解,最后一行中&x实际表示是一个类型为int (*)[4]类型的指针,所以&x+1后地址增加16。

 有一个和上面类似的程序(源自《C语言深度剖析》,可以在网上搜索下载到):

#include <stdio.h>

int main(void)
{
   int a[] = {1,2,3,4,5};
   int *p = (int *)(&a+1);
   printf("%d %d\n",*(a+1),*(p-1));
   return 0;
}

  此程序输出结果为2,5

类似的还有个程序:

#include <stdio.h>

int main(void)
{
    int a[] = {1,2,3,4,5,6,7,8,9,10};
    int (*p1)[3] = &a;
    int (*p2)[4] = a;
    printf("%d %d\n",*(*(p1+1)+1),*(*(p2+2)+1));
    return 0;
}

  该程序编译时会提示警告(p1和p2初始化采用不兼容的指针类型)。

  这里,p1和p2都是数组指针。p1指向有三个元素的整型数组,p2指向有四个元素的整形数组。

  数组指针p1类似于一个二维数组b[][3],而根据二维数组b[i][j]可以表示成*(*(b+i)+j)的形式。所以*(*(p1+1)+1)相当于二维数组b[][3]中的b[1][1],因而对应于a[4],所以输出结果为5,类似的可以得出另一个指针的输出结果。

所以整个输出结果为5,10

同样,还有个程序:

#include <stdio.h>
int main(void)
{
    int a[4] = {1,2,3,4};
    int *ptr1 = (int*)(&a+1);
    int *ptr2 = (int *)((int)a+1);
    printf("%x,%x\n",ptr1[-1],*ptr2);
    return 0;
}

  ptr1[-1]的值(跟上面的例子的情况类似)为4

  而*ptr2的值则根据处理器的不同而可能有不同的结果(参见大端模式和小端模式 endianness

  如果为小端模式(例如intel x86兼容处理器,8051,avr等),那么*ptr2(注意使用题目中使用了%x输出格式)输出结果为2000000

  如果为大端模式(例如motorola 68k,powerpc,IBM sys/360等),那么*ptr2输出结果为100

判断处理器使用什么模式,可以使用下面函数进行检测(相关链接 ):

/*little endian when return 1,else  big endian*/
int CheckEndian()
{
    union{
        int i;
        char c;
    }e;
    e.i = 1;
    return (e.c == 1);
}

  如果使用大端模式,那么数组a在内存中表示(十六进制)为 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04

ptr2指向第二个00,所以为00 00 01 00(即0x100)

  如果是小端模式,那么数组a在内存中表示为01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00,ptr2指向第一个00,所以其指向内容为00 00 00 02,由于使用了小端模式,所以需要颠倒过来表示即02000000(也即0x2000000).

接着是一些二维数组和二级指针的一些例子(同样源自《c语言深度剖析》):

 

#include <stdio.h>

int main(void)
{
    int a[3][2]={(1,2),(3,4),(5,6)};
    int *p;
    p = a[0];
    printf("%d\n",p[0]);
    return 0;
}

 编译后运行,结果为2,因为圆括号内的使用了逗号表达式,二维数组a的初始化相当于int a[3][2]={2,4,6};

 

#include <stdio.h>
int main(void)
{
    int a[5][5];
    int (*p)[4];
    p = a;
    printf("a_ptr=%#p,p_ptr=%#p\n",&a[4][2],&p[4,2]);
    printf("%p,%d\n",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);
    return 0;
}

 编译后运行,结果为

a_ptr=0X0022FF70,p_ptr=0X0022FF38
FFFFFFFC,-4

 这是因为二维数组实际上仍然用一维数组来表示。而int (*p)[4]相当于把a的一维数组表示又转化成二维数组[][4],这样&p[4][2]相当于p+4*4+2,&a[4][2]相当于p+4*5+2,所以二者相减结果为-4.

 

接着是几个内存分配的程序(源自《高质量程序设计指南--c++/c语言》)

(1)

#include <stdio.h>

void getmemory(char *p)
{
    p = (char*)malloc(100*sizeof(*p));

}
int main(void)
{
    char *str = NULL;
    getmemory(str);
    strcpy(str,"hello,world");
    printf("%s\n",str);
    
    return 0;
}

  编译运行后程序会发生崩溃,因为getmemory只是将NULL值传给参数p,然后又给p分配了100个字节空间,对str没有任何改变。由于str仍未NULL,所以对空串进行串拷贝会发生崩溃。

(2)

#include <stdio.h>

char *getmemory(void)
{
    char p[] = "hello,world";
    return p;
}
int main(void)
{
    char *str = NULL;
    str = getmemory();
    printf("%s\n",str);

    return 0;
}

 编译运行该程序,其输出结果为乱码。

这是因为C语言中栈帧布局可知,getmemory被调用后,会在栈上分配数组p来存放"hello,world"字符串并返回该串地址,但是在getmemory返回后,在栈上分配的数组部分已经变成无效状态,此时调用printf函数,就会覆盖掉原来数组p中的内容。但是输出串地址仍是以前的地址,所以可能输出乱码。

(3)

#include <stdio.h>

void getmemory(char **p, int num)
{
    *p = (char*)malloc(num);
}
int main(void)
{
    char *str = NULL;
    getmemory(&str,100);
    strcpy(str,"hello,world");
    printf("%s\n",str);

    return 0;
}

 编译运行该代码会输出”hello,world",但是该程序没有将分配空间释放,所以可能会产生内存泄漏

(4)

#include <stdio.h>

void test(void)
{
    char *str = (char*)malloc(100);
    strcpy(str,"hello");
    free(str);
    if(str != NULL)
    {
        strcpy(str,"world");
        printf("%s\n",str);
    }
}
int main(void)
{
    test();
    return 0;
}

 编译运行该程序后,可能产生非常危险的后果。因为前面给str分配空间并释放,但是并没有将str设置为NULL,因而str成为“野指针”,下面还要继续对str原来位置复制一个串"world"并输出,这就成了篡改堆中内容,可能带来非常严重的后果。

 

 

接下来是一个要求不用循环和条件语句输出1到1000的所有整数(来源 )。

(方法1):该方法会产生一个错误(除0故障),但能输出正确结果

 

#include <stdio.h>
#define MAX 1000
int boom;
int foo(n) {
    boom = 1 / (MAX-n+1);
    printf("%d\n", n);
    foo(n+1);
}
int main() {
    foo(1);
}

 (方法二):

 

#include <stdio.h>
#include <stdlib.h>

void f(int j)
{
    static void (*const ft[2])(int) = { f, exit };

    printf("%d\n", j);
    ft[j/1000](j + 1);
}

int main(int argc, char *argv[])
{
    f(1);
}

 这段代码可以简化为:

 

#include <stdio.h>
#include <stdlib.h>

void main(int j) {
  printf("%d\n", j);
  (&main + (&exit - &main)*(j/1000))(j+1);
}

  运行此程序时,由于不带任何参数,所以j的初始值为1(相当于argc参数,只是这里变量名换一下而已,不影响程序的执行),然后输出1,下一句中j/1000值为0(j为1-999之间任意整数时其值均为0),所以相当于执行(&main)(2),这是一个函数指针调用,然后输出2,继续执行下去直至j为999时,会调用(&main)(1000),此时输出1000,j/1000值变为1,所以下一步调用(&main+(&exit-&main))(1001),即exit(1001),此时使用exit跳出函数的执行。

 

 

不使用中间变量交换两个整型变量的值,代码如下:

int x,y;
x=x^y;
y=x^y;
x=x^y;

 

Duff's device :

 

send(to, from, count)
	register short *to, *from;
	register count;
	{
		register n=(count+7)/8;
		switch(count%8){
		case 0:	do{	*to = *from++;
		case 7:		*to = *from++;
		case 6:		*to = *from++;
		case 5:		*to = *from++;
		case 4:		*to = *from++;
		case 3:		*to = *from++;
		case 2:		*to = *from++;
		case 1:		*to = *from++;
			}while(--n>0);
		}
	}

 这个代码格式是老的代码格式。具体讲解见上面的链接。

 

检查一个字符串(名称为s1)是否是另外一个字符串(名称为s2)的旋转版本(来源 )。例如"stackoverflow"的旋转版本字符串有: "tackoverflows",“overflowstack"等。

算法实现方法如下:

(1)确定两个串长度相等

(2)将s1和s1连接起来,检查s2是否是连接后的串的字串

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int IsRotation(char s1[], char s2[])
{
  int len1 = strlen(s1),len2=strlen(s2);
  char *str = malloc((len1+len1+1)*sizeof(char));
  if(len1 != len2)
    return 0;
  if(str == NULL)
    {
      fprintf(stderr,"error while allocating space\n");
      return -1;
    }
  if(strcpy(str,s1) == NULL || strcat(str,s1) == NULL)
    {
      fprintf(stderr,"error while copying or concatenate string s1 to str\n");
      return -1;
    }
  if(strstr(str,s2) != NULL)
    return 1;
  return 0;
}

 这段代码是我自己写的,可能有不完善的地方。当执行中出错时返回-1,如果是旋转串则返回1,否则返回0.

分享到:
评论

相关推荐

    必须弄懂的495个C语言问题.pdf )

    ### 必须弄懂的495个C语言问题 #### 1. 声明和初始化 **1.1 如何决定使用哪种整数类型?** 在C语言中选择整数类型时,应考虑所需的范围和存储空间。常见的整数类型包括`char`、`short`、`int`、`long`以及`long ...

    c语言的预编译处理

    预编译处理虽然不难,但是学好C语言最好能弄懂预编译

    C语言迅速提升水平

    认真的读完这一笔记,每个序号都弄懂,相信你的c语言的基础就可以了,接下来就是怎么建设成一栋大楼了

    2022年c语言学习心得体会.docx

    在每一节课上,留意老师讲的内容,有的学问,老师略微提点,你就能弄懂,但是自己看书,或许你几天也弄不懂。老师更能清楚地让你明白所要求驾驭的学问点。在课堂上,尽可能多的在草稿纸上写下你自己的代码,让老师看...

    C语言的二维数组的指针访问

    如果真正弄懂了这个问题,就可以说你学会了使用指针。二维数组的指针指向二维数组的存储地址。在内存中,二维数组的存储地址与一维数组的存储地址相似,都具有元素地址和行首地址。 二、理解行首地址和元素地址 在...

    C语言课程设计 图书管理系统(项目源码+课程报告+手敲视频教程)

    1. 看得懂:C语言课程设计是如何开发和搭建的 2. 学得会:指针的知识逻辑,链表的链接过程 3. 搞得清:链表,指针,结构体,文件存储,数组的知识 4. 弄得明:程序系统的运行逻辑和交互方式 阅读建议: 这是我在...

    编程------C语言源代码

    描述中的“可以更加容易学习弄懂C语言结构”暗示我们将关注C语言的程序结构,包括函数定义、声明、控制流结构(条件语句和循环)以及内存管理。在C语言中,内存管理是手动进行的,通过使用malloc()和free()函数来...

    嵌入式系统复习题资料整理

    计算机图形学实验报告完整资料整理(25道嵌入式习题的经典解答) 什么是嵌入式系统?...阅读弄懂P234,P236的例题 在嵌入式C语言程序设计中为提高程序效率的相关设计技巧。 S3C44B0X的内部各功能模块的用途

    C语言银行管理系统设计.doc

    解决方法:上网查资料弄懂不认识的程序,并向教师和会的同学请教。 2. 程序错误较多,无法执行。解决方法:检查程序的语法和逻辑错误,并进行Debug。 五、实现方法 银行存取款管理系统的实现方法是使用C语言来编写...

    c语言学习心得.docx

    * 读懂课本上的每一个例题和习题的代码,弄懂每一章的内容是什么?在 c 语言中有什么用? * 尝试修改每一个例题的代码,采用不同的代码来实现题目的要求。 二、课堂学习 * 注意老师讲的内容,把老师讲的内容与课本...

    C语言——指针

    我虽然很早就接触了C语言,但是一直没有将它学懂,这个寒假将C语言的一些知识又重新学了一遍,弄清楚了之前一直没有明白的指针,现将近期所学分享给大家,希望能够帮助到大家。 2.什么是指针 任何程序数据载入内存后...

    C语言编写自绘ListBox控件,练习小作品

    看了MSDN的例子,自己照着样子弄了一下, ...弄懂了这个就是简单的和VB一样,我要继续学习GDI的操作, 有了GDI的基础,就可以为以后制作报表打好前提基础了。 练习作品,只是偿偿鲜,没有整理代码,乱七八糟。

    【JavaScript源代码】一篇文章弄懂javascript内存泄漏.docx

    【JavaScript源代码】一篇文章弄懂javascript内存泄漏 在JavaScript中,内存管理对于程序性能至关重要,因为内存泄漏会导致程序效率下降,甚至可能导致应用崩溃。本文旨在深入解析JavaScript中的内存泄漏及其解决...

    C语言学生成绩管理系统课程设计

    不管自己能不能够完成,最重要的是能弄懂。参考其他资料,试着自己编写是不错的选择。这个课程设计也是我参照资料,自己编写的。自己适当地增加了一些功能。不过,编的不够那么专业吧。 #include #include #include...

    基于C语言实现的单线程、轻量级和高性能的内网穿透程序源码(支持TCP流量转发).zip

    【项目说明】 ...5、如果确定自己是个学渣,想拿来作毕设、课设直接用也无妨,但自己还是尽可能弄懂项目!也可以与我交流 强调:项目名字和路径不要有中文,解压重命名为英文名字后再运行!项目易上手

Global site tag (gtag.js) - Google Analytics