美文网首页
C语言中的存储器和指针

C语言中的存储器和指针

作者: suikaJY | 来源:发表于2018-06-29 10:00 被阅读0次

使用指针的原因

  1. 在函数调用时传递一整份数据的引用
  2. 让两段代码共享一个存储空间,而不是传递一个副本

不同变量在存储器中的位置

存储器由五个部分组成:

  1. 全局量
  2. 常量段
  3. 代码段

变量的存储位置:

  • 在函数中声明的变量的地址在栈中
  • 在函数外声明的变量的地址在全局量中

全局变量永远只有一个地址,但局部变量不是,如果程序中存在递归函数,那么递归函数中的局部变量在栈中的个数取决于当前递归层数。

指针操作

拿变量var和他的指针p为例:

  1. 取变量的地址&var
  2. 取地址中的内容*p(这里等同于直接使用p指向的var)
  3. 改变地址中的内容*p = 3;,这里等同于var = 3;

指针的相关问题

  1. 指针是进程中真实的存储编号,而不是某种形式的引用
  2. 为什么存储器是进程的:计算机会为每一个进程分配一个简版存储器,看起来就像是一长串字节
  3. 存储器并不仅仅是一长串字节。存储器本身其实复杂很多,但实现细节对进程隐藏了起来。这样操作系统就可以在存储器中移动进程,或者释放并添加到别的位置。
  4. 物理存储器的结构十分复杂,计算机通常会将存储器分组映射到存储芯片的不同存储体(memory bank)
  5. 对大部分程序来说不需要关心机器组织存储器的细节
  6. 打印指针需要使用%p的格式化格式。
  7. 为什么%p是十六进制的:因为工程师通常使用十六进制表示地址
  8. 将指针转化成一般数字:大部分计算机中可以使用long a=(long)p来将指针转化成一般数字,但并非所有操作系统都是这样。

sizeof运算符

用来获取某样东西在存储器中占了多少个字节,既可以对某条数据使用,也可以对数据类型使用

  • sizeof(int),多数计算机返回4
  • sizeof("Turtles!"),返回9,因为是8个字符加一个'\0'

sizeof它和函数不同,sizeof的结果其实在编译时就已经确定。

对函数传递字符串

考虑如下程序:

#include <stdio.h>

void fun(char msg[]) {
    printf("message is %s\n", msg);//message is cookies make you fat
    printf("message occupies %i bytes\n", sizeof(msg));//message occupies 4 bytes
}

int main() {
    char msg[] ="cookies make you fat";
    fun(msg);
    return 0;
}

fun函数的参数是字符串数组类型,而在C语言中数组变量本身就是一个指针,此刻的sizeof(msg)其实相当于sizeof(char *),所以会只占四个字节。

结论:

  • 对函数传递一个数组时数组本身不会携带数组长度信息,通常要程序员自己给一个数组长度当作参数。
  • 将数组当做函数参数这种做法不好,不如直接使用指针传参(编译器也会在编译时警告一下)。这会导致指针退化问题

数组变量和指针的区别

考虑如下代码:

char s[] = "How big is it?";
char *t = s;
  1. sizeof(数组)是数组的大小,而sizeof(指针)就一般是8或者4。这里的数组是指同一代码块中的
  2. 数组的地址是数组的地址,而指针的地址是二级指针。
    • &s == s
    • &t != t
  3. 数组变量不能指向其他地方
    • 创建指针变量时计算机会分配给指针变量4或8个字节,但如果是数组则不会去分配字节,计算机仅分配数组内容的空间。
    • 编译器仅仅是在数组变量出现的地方将其替换成了数组的起始地址,所以它不能指向别的地方
    • s = t,这句会报编译错误

指针退化

当数组变量赋值给指针时,指针变量只会包含数组的地址信息,而对数组的长度一无所知。相当于丢失了一些信息。我们把这种信息的丢失成为退化

为何数组从0开始?

考虑如下代码:

int drinks[] = {4,2,3};
printf("第一单:%i 杯\n", drinks[0]);//4
printf("第一单:%i 杯\n", *drinks);//4

也就是说:drinks[0] == *drinks

另外有drinks[i] == *(drinks+i),对指针做加法时加几位指针就移动对应类型的位数,所以数组从0开始可以方便的找出数组中每一位的值。所谓索引其实也就是地址单元。

つまり:drinks[2] == *(drinks+2) == *(2+drinks) == 2[drinks](注:这里最后那种写法现在已经会导致运行崩溃)

代码示例 - 输出子串:

void subStrPrint(char *oriStr, int beginIndex) {
    printf("%s\n", oriStr + beginIndex);
}

int main() {
    char *s = "hello world";
    subStrPrint(s,5);//world
    return 0;
}

指针变量的类型

为了便于指针运算,C语言中的指针携带了类型信息。这样在指针做加减法是可以根据类型所占字节改变指向

指针运算的问题:

  • 指针运算需要理解吗:有的程序员不使用指针运算,因为它容易出错,但指针运算可以方便地处理数组数据。
  • 指针能做减法么:可以,但要当心不要小于数组起点
  • C语言什么时候对指针算术运算做出调整:在编译期,指针运算p+2在编译时会将2添加一个乘法运算,比如p的指针类型是int,那么p+2就会被编译成p+sizeof(int)*2,然后得到的地址可能是p加8或加16,取决于计算机系统位数。这里的sizeof(int)也是在编译时期确定的,编译后也只是一个值
  • 指针可以相乘么:不可以

用指针获取输入:scanf和fgets

1. scanf()

利用数组指针scanf可以更改字符数组中的值

int main() {
    char name[40];
    printf("please input your name: ");
    scanf("%39s", name);//读取39个字符,最后会填上一个'\0'
    printf("your input is %s", name);
    return 0;
}

也可以连续输入:

int main() {
    char first_name[20];
    char last_name[20];
    printf("please input your name: ");
    scanf("%19s %19s", first_name, last_name);//读取两个字符,中间用空格分开,输入:suika jy
    printf("your input is FirstName: %s, LastName: %s ", first_name, last_name);//输出:your input is FirstName: suika, LastName: jy
    return 0;
}

在获取输入时通常对%s加一个数量限制,否则用户输入的字符个数超过了字符数组长度时,多于的字符就会被装到后续空间中,而后续的空间里面都是野指针或者是已经被其他变量占用的地址。很有可能会指到了不改指的地方。这时scanf会导致缓冲区溢出。这种情况通常被称为段错误或者abort trap,并导致程序崩溃。

2. fgets()

fgets的主要优势是使用方便,加上了输入长度限制不会导致段错误。如果传入字符数组的话与sizeof一起食用更佳,传入指针则需要自己指定输入长度。fgets在指定长度时包括了哨兵字符,所以无需自己减一。

int main() {
    char food[5];
    printf("Enter your food:");
    //第一个参数为字符串指针,第二个参数接收字符串长度(包括了'\0',所以这里最长有4个字符),第三个参数是输入来源
    //stdin表示输入来源是键盘
    fgets(food, sizeof(food), stdin);
    printf("your food is : %s", food);
    return 0;
}

fgets衍生自gets,gets是一个没有限制的函数,不推荐使用

scanf和fgets的对比

情境 scanf fgets
限制用户输入字符 使用不方便 使用方便
输入多个字段 支持 不支持
用户输入带空格 因为%s遇到空格就会停止,所以需要一些正则技巧 完美支持

字符串字面值不能修改

指向字面值的字符串变量不能用来修改字符串内容:

char *cards = "JQK";//不能用这个变量修改字符串

但如果用字符串字面值创建一个数组就可以修改了:

char cards[]= "JQK";

这是由于C语言使用存储器的方式决定的
原理是:

  • 字符串的字面量会被保存到存储器中的常量区用指针时指针会直接指向常量区的字符数组,所以修改时修改的是常量区的值,所以会报段异常。
  • 用数组方式表示字符串时可以直接修改原理是:数组在创建时会在栈中声明一块存储空间,这时会将字符串常量"JQK"做一份拷贝并赋值给栈空间中的char数组。修改时修改的是栈空间中的内容,所以不会报错。(注:这里和Java的字符串声明很像,一个字面量会产生两个字符串)
    @存储器分区
    如果在函数参数中有类似void fun(char cards[])的声明时,就直接把cards当做指针即可,因为这是个指针退化的过程

针对这个问题的一个编码技巧/规范

为了避免这个错误可以不再将char指针设置为字符串字面值,像这样:

char *s = "Some string";

但是把指针设为字符串字面值又没错,问题出在你试图修改字符串字面值。如果你想把指针设成字符串字面值,必须确保使用了const关键字:

const char *s  = "Some string";

这样一来,如果编译器发现有代码试图修改字符串,就会提示编译错误:
s[0]='a';
monte.c:7: error: assignment of read-only location

相关问题:

  • const是什么意思:const不会影响存储细节,他只是表示当你修改const修饰过的变量时编译器会报错。在C语言中const在运行时是可擦除的,通过获取const变量的指针可以变相修改const修饰的变量的值。
  • 存储器段总是以相同顺序出现吗:不是,不同操作系统之间略有差异,windows中代码段不在低位。
  • 数组变量为什么没有保存在存储器里面:程序在编译期,会把所有对数组变量的引用替换成数组的地址,也就是说在最后的可执行文件中,数组变量并不存在。然而数组变量从来不需要指向其他地方,那么有和没有其实都一样

存储器分区

    • 这是存储器用来保存局部变量的部分。每当调用函数,函数的所有局部变量都在沾上创建。他之所以叫栈是因为他看起来就像堆积而成的栈板:当进入函数式,变量会放到栈顶,离开函数时,把变量从栈顶拿走。栈做起事来颠三倒四,他从存储器的顶部开始,向下增长。
    • 堆用于动态存储:程序在运行时创建一些数据,然后使用很长一段时间。
  1. 全局量
    • 全局量位于所有函数之外,并对所有函数可见。程序一开始运行时就会创建全局量。
  2. 常量:
    • 常量也在程序一开始运行时创建,但他们保存在只读存储器中。常量是一些在程序中要用到的不变量,你不会想修改他们的值,例如字符串字面值。
  3. 代码:
    • 最后是代码段,很多操作系统都把代码放在存储器地址的低位。代码段也是只读的,它是存储器中用来加载机器代码的部分。

相关文章

  • C语言中的存储器和指针

    使用指针的原因 在函数调用时传递一整份数据的引用 让两段代码共享一个存储空间,而不是传递一个副本 不同变量在存储器...

  • C语言05- 指针

    C语言05- 指针 13:指针 指针是C语言中的精华,也是C语言程序的重点和难点。 13.1:指针定义与使用 指针...

  • Go语言基础之指针

    区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。 要搞明白Go语言中的指针需要先知道3个...

  • 指针

    指针的基础、 c语言中的指针,可以使程序简洁,高效,紧凑。在计算机内部存储器中,没一个字节单元都有一个编号,称之为...

  • iOS开发 -- C语言基础8(指针)

    iOS开发 -- C语言基础8(指针) 指针是C语言中非常重要的数据类型,如果你说C语言中除了指针,其他你都学得很...

  • 13-Go语言指针和方法

    指针 普通数据类型指针 Go语言中的普通指针和C语言中的普通指针一样, 通过指针也可以间接操作指向的存储空间 Go...

  • Golang 指针

    指针 区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针。Go语言当中的指针涉及到三个点:指...

  • Go语言-指针

    Go语言中的指针不同于C语言,Go语言的指针使用方法要简单很多。当然和C语言从指针定义到指针的使用都有很大的不同。...

  • 11-指针

    一、指针 1.指针的重要性指针是C语言中非常重要的数据类型,如果你说C语言中除了指针,其他你都学得很好,那你干脆说...

  • ndk02_指针运算,函数参数与指针,数组指针,二级指针

    一、指针运算 二、数组与指针 三、指针和函数参数 四、指针数组 五、 二级指针 六、知识要点 1、C语言中的函数如...

网友评论

      本文标题:C语言中的存储器和指针

      本文链接:https://www.haomeiwen.com/subject/eaboyftx.html