C语言中连续定义两个变量,为什么地址是这样的?

论坛 期权论坛 期权     
匿名用户1024   2021-5-14 21:07   6242   5
环境:64位系统,Xcode的clang编译器
以前听老师分析内存,说变量分配的内存是从下到上。那么后定义的变量地址要小一些。
在知道两个变量的类型后,可以知道连续定义变量的地址相差多少!
补:可能是以前老师讲数组时那么分析的吧,不知道我记没记错。所以我把当时做的笔记附在最后,大家帮我看看笔记是否正确,谢谢。


如上面两个变量是连续定义的,c占1字节、a占据4个字节。
但是为什么地址不是间隔4?

其实我以前连续定义了2个int类型的变量,再查看地址,的确是相差4。所以我感到奇怪,像题图那种情况,Xcode会有特殊的处理机制吗?

注:我正在学习结构体,老师提到了对齐(补齐)算法(不懂)。
不知道定义的变量会不会有对齐算法?以前没有注意过变量地址的问题。
因为刚刚学习结构体,所以想到了这个问题。
在结构体中我理解的是:
struct Student
    {
        int age;// 4
        char *name;//8
    };
    struct Student stus[3];
   printf("%ld\n", sizeof(stus));
//根据char *类型占据的字节倍数计算,8+4不是8的倍数,所以结果是8乘2得16(不知道是我理解的是不是正确?)

关于结构体,我想问的就是如何解释sizeof(结构体变量)的计算方式


上面那段代码中结构体变量只有两个成员分别占用4字节和8字节,但结构体大小是16字节。通过指针间接取值时,是取成员age时,取4字节。取成员name时,取8字节吗?
补充一个问题:老师讲指针时说,指针有类型是为了方便取值、间接改值。结构体变量的大小有特殊计算,如指向结构体的指针,操作指针是怎么正确获取成员的值,难道是根据每个结构体成员分别占据的大小,来取值的?


附上笔记:


分享到 :
0 人收藏

5 个回复

倒序浏览
2#
有关回应  16级独孤 | 2021-5-14 21:07:23 发帖IP地址来自
不要试图去固化理解编译器的行为,尤其是C语言规范中没有定义的行为

这些行为包括但不限于:变量分布,++/--混用的时候的优先级,函数调用时参数的计算次序等等

你这个问题里,局部变量在栈上的排列次序是语言标准中没有规定的内容,所以:

它们怎么排列,间隔是多大,完全看编译器的设计者,换句话说,可能是任何形式。

如果你们老师非要说局部变量地址一定是从下到上,那么,你们老师错了

举几个例子:

1. 在比较老的编译器里,如果没有对变量取地址的操作,那么有些局部变量是通过寄存器保存的,不占栈上内存,根本不存在内存中如何排列的问题,比如TurboC 2.0这种。

2. 在一些较新的编译器里,局部变量有些是根据使用频率来排列的,这样可以降低cache的miss率,所以怎么排列完全根据使用频率。

3. 大部分主流编译器的局部变量地址确实是从下到上,但也有反过来的。从下到上排列的好处之一是编译器处理一个函数的时候,可以动态增长栈的长度,不需要先判断局部变量有多少个。

4. 还有一些非主流编译器会调整变量的排列次序,使得其在各个变量基本对齐的情况下占用的栈空间最小,对于一些嵌入式设备来说,非常有用。

所以,你的第一个问题,如果你非要研究编译器,也可以,但这不是常态,不同编译器行为可能不同。同一款编译器在不同平台上也可能不同。

第一个问题里,a的地址范围是XXXX6BE8-XXXX6BEB,中间空出3个字节,然后是c的地址,正好对齐到4字节。至于为什么c的地址是XXXX6BEF,而不是XXX6BEC,这个可能就是编译器的特性吧。

第二个问题:结构体占的字节大小,这个确实会有对齐的情况,但除非你用packed之类的编译参数指定,否则在不同的硬件平台上,效果可能也不完全一样。
3#
有关回应  16级独孤 | 2021-5-14 21:07:24 发帖IP地址来自
确实是对齐的缘故。
“变量分配的内存是从下到上”这话听起来有点奇怪。。。
实际做法按照定义的顺序分配地址,地址是从上往下分配的。
所以首先设置好c的地址。
接着,a的地址必须是4的整数倍,从c的地址0xef往下,第一个4的整数倍是0xec,但是它们之间只有3个空位,所以没法用。
于是a必须再往下找下一个4的整数倍,即0xe8。

贴另一段代码
int main(){
    char c='A';
    char d='A';
    char e='A';
    char f='A';
    int a=10;
    printf("a=%p\n",&a);
    printf("c=%p\n",&c);
    printf("d=%p\n",&d);
    printf("e=%p\n",&e);
    printf("f=%p\n",&f);
}
f和a的距离就是4了。
4#
有关回应  16级独孤 | 2021-5-14 21:07:25 发帖IP地址来自
题主遇到的这个情况,就是C标准中的“未定义行为”。


我们编写的C语言代码,应该被编译器编译成何种指令,是有国际标准的,然而,这个标准,本身具有着“魔性”的不健全


与Java不同,Java由一个公司开发,其运行的环境也有该公司提供服务,Java的每一行代码,几乎可以认定为在机器上具有绝对一致性的表现。
然而,C在发展过程中,出现了很多可圈可点的编译器,包括(远远不限于):
Turbo C、MSVC、gcc、clang……
不同的编译器,实现的过程中,难免出现了个性化的操作,加上C语言在程序开发语言的历史中诞生的较早,这些魔性化的个性,被或多或少的保留下来


进一步促成这些个性保留的,是滞后了的规范。C语言国际规范的产生,远远后于C语言的产生。
如果题主感兴趣,可以翻看各个百科,里面有较为详尽的介绍,这里不啰嗦了。


由于各个编译器太老大,太霸气了,C语言标准的制定,充分考虑到了这些情况,于是,标准中,明确规定了“未定义行为”,定义成了“未定义”是否很有谬论的魔性~
然而,事实如此。
比如,以我们熟知的字符串拷贝函数“strcpy()”为例,国际标准中明确规定了(原文过长,引用略,有兴趣的,可以参看7.24.2.3节,第2段)节选引用如下:
… If copying takes place between objects that overlap, the behavior is undefined.
相对于标准中明确指出的“the behavior is undefined”的显示的未定义情况,C标准中存在着更多的情况,根本没有描述


题主遇到的这个情况,就是。


具体的说,就是C语言国际标准中,没有规定栈地址局部变量在内存中的对齐方式,各个编译器完全可以依照自己的喜好来进行实现。
这种情况,就是C语言国际标准中的“法律空白”。
5#
有关回应  16级独孤 | 2021-5-14 21:07:26 发帖IP地址来自
我是这样理解的:你定义一个变量A、系统就随便在RAM里找一块刚好能用的放下来、再定义一个B、再找一块、如果还要用一个指针去定义刚刚A放哪了、然后放下B、再移动指针、我觉得这样多此一举、因为你是分开定义的、你访问完A不一定要访问B、所以这个功能有点多余、干脆就随便放、数组是用下标来找的、第i个刚好是偏移了(i-1)*sizeof(数据类型)、很好找、如果每个元素都是随便找一个位置放下来的话估计就不这么好找了、挺符合C++不是必要尽量不加的思想的。。。
6#
有关回应  16级独孤 | 2021-5-14 21:07:27 发帖IP地址来自
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:136515
帖子:27303
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP