android 源码阅读的C++语法准备6 动态内存 信号处理 线程操作

动态内存

C++ 程序中的内存分为两个部分:

  • 栈:在函数内部声明的所有变量都将占用栈内存。
  • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。

很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。

在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。

如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。

malloc() 函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。

new 关键字

new data-type;

在这里,data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。

#include <iostream>

using namespace std;

int main() {
    double *pvalue = NULL; // 初始化为 null 的指针
    pvalue = new double;   // 为变量请求内存

    // 如果自由存储区已被用完,可能无法成功分配内存。
    // 所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作
    double *pvalue2 = NULL;
    if (!(pvalue2 = new double)) //为NULL
    {
        cout << "Error: out of memory." << endl;
        exit(1);
    }
    // 在分配的地址存储值
    *pvalue = 29494.99;
    cout << "Value of pvalue : " << *pvalue << endl;

    delete pvalue;// 释放内存
    delete pvalue2;
}

void test_array() {
    const int m = 3;
    const int n = 3;
    /*一维数组*/
    // 动态分配,数组长度为 m
    int *array = new int[m];
    //释放内存
    delete[] array;



    /*二维数组*/
    int **array2;
    // 假定数组第一维长度为 m, 第二维长度为 n
    // 动态分配空间
    array2 = new int *[m];
    for (int i = 0; i < m; i++) {
        array2[i] = new int[n];
    }
    //释放
    for (int i = 0; i < m; i++) {
        delete[] array2[i];
    }
    delete[] array2;
}

对象的动态内存分配

class Box {
public:
    int num;
    Box() {
        cout << "调用构造函数!" << endl;
    }
    
    Box(int num) {
        this.num = num;
        cout << "调用构造函数!" << endl;
    }

    ~Box() {
        cout << "调用析构函数!" << endl;
    }
};

void test_object() {
    //形式1,声明一个 box1,类型为 Box
    Box box1;// 直接声明一个对象,相当于box1()
    //Box box2(1);//隐式调用A带一个参数的构造函数
    //Box box3 = Box(2);//显式调用A带一个参数构造函数
    
    
    //形式2
    Box *myBox = new Box;
    delete myBox;
    
    //新建对象数组
    Box *myBoxArray = new Box[4];
    delete[] myBoxArray; // 删除数组
}

形式1:调用对应的构造函数在栈中创建对象,使用完毕后,系统自动回收对象内存,无需手动释放。
形式2:在堆内存中动态开辟空间创建对象,需要手动释放内存。

C 语言内存管理

C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。

序号 函数和描述
1 void *calloc(int num, int size); 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。
2 void free(void *address); 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
3 void *malloc(int num);堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
4 void *realloc(void *address, int newsize); 该函数重新分配内存,把内存扩展到 newsize
5 free() 释放分配的内存

注意:void * 类型表示未确定类型的指针。C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。C 语言中 void* 详解及应用

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

//
// Created by rentianxin on 19-3-27.
//
int main() {
    char name[100];
    char *description;
    strcpy(name, "Zara Ali");

    /*动态分配内存*/
    description = (char *) malloc(30 * sizeof(char));
    //可以替换为如下的形式
//    description = (char *) calloc(200, sizeof(char));

//判断有没有分配成功
    if( description == NULL )
    {
        fprintf(stderr, "Error - unable to allocate required memory\n");
    }
    else
    {
        strcpy( description, "Zara ali a DPS student in class 10th");
    }
    printf("Name = %s\n", name );
    printf("Description: %s\n", description );

    /* 假设您想要存储更大的描述信息 */
    /*不重新分配额外的内存,strcat() 函数会生成一个错误,因为存储 description 时可用的内存不足*/
    description = (char *) realloc( description, 100 * sizeof(char) );
    if( description == NULL )
    {
        fprintf(stderr, "Error - unable to allocate required memory\n");
    }
    else
    {
        strcat( description, " She is in class 10th");
    }

    printf("Name = %s\n", name );
    printf("Description: %s\n", description );

    /* 使用 free() 函数释放内存 */
    free(description);
}

信号处理

信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。

有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件 <csignal> 中。

信号 描述
SIGABRT 程序的异常终止,如调用 abort
SIGFPE 错误的算术运算,比如除以零或导致溢出的操作。
SIGILL 检测非法指令。
SIGINT 接收到交互注意信号。
SIGSEGV 非法访问内存。
SIGTERM 发送到程序的终止请求。

signal() 函数--处理信号

C++ 信号处理库提供了 signal 函数,用来捕获突发事件。

void (*signal (int sig, void (*func)(int)))(int); 

这个函数接收两个参数:第一个参数是一个整数,代表了信号的编号;第二个参数是一个指向信号处理函数的指针。

raise() 函数--生成信号

您可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数,语法如下:

int raise (signal sig);

在这里,sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。

#include <iostream>
#include <csignal>
#include <unistd.h>

using namespace std;

void signalHandler (int signum) {
    cout << "Interrupt signal (" << signum << ") received.\n";

    // 清理并关闭
    // 终止程序
    exit(signum);
}

int main ()
{
    // 注册信号 SIGINT 和信号处理程序
    signal(SIGINT, signalHandler);

    int i = 0;
    while(++i){
        cout << "Going to sleep...." << endl;
        if( i == 3 ){
            raise( SIGINT);
        }
        sleep(1);/*linux 让线程挂起,使用sleep,参数为秒*/
    }

    return 0;
}

多线程

多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。

假设您使用的是 Linux 操作系统,我们要使用 POSIX 编写多线程 C++ 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用,比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。

#include <pthread.h>

线程Id

typedef unsigned long int pthread_t;    //come from /usr/include/bits/pthread.h

用途:pthread_t用于声明线程ID。

sizeof (pthread_t) =4;

创建线程

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) 

pthread_create 创建一个新的线程,并让它可执行。下面是关于参数的说明:

参数 描述
thread 指向线程标识符指针
attr 一个不透明的属性对象,可以被用来设置线程属性。您可以指定线程属性对象,也可以使用默认值 NULL。
start_routine 线程运行函数起始地址,一旦线程被创建就会执行。
arg 运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数,则使用 NULL。

创建线程成功时,函数返回 0,若返回值不为 0 则说明创建线程失败。

终止线程

#include <pthread.h>
pthread_exit (status) 

pthread_exit 用于显式地退出一个线程。通常情况下,pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。

如果 main() 是在它所创建的线程之前结束,并通过 pthread_exit() 退出,那么其他线程将继续执行。否则,它们将在 main() 结束时自动被终止。

#include <iostream>
// 必须的头文件
//cmake LINK_LIBRARIES(pthread)
//编译 g++ test.cpp -lpthread -o test.o
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

// 线程的运行函数
void *say_hello(void *args) {
    cout << "Hello Runoob!" << endl;
    return 0;
}

void *printHello(void *threadid) {
    // 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
    int tid = *((int *) threadid);
    cout << "Hello Runoob! 线程 ID, " << tid << endl;
    pthread_exit(NULL);
}

int main() {
    // 定义线程的 id 变量,多个变量使用数组
    pthread_t tids[NUM_THREADS];
    //不传参
//    for (int i = 0; i < NUM_THREADS; ++i) {
//        //参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
//        int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
//        if (ret != 0) {
//            cout << "pthread_create error: error_code=" << ret << endl;
//        }
//    }

    //传参
    pthread_t threads[NUM_THREADS];
    int indexes[NUM_THREADS];// 用数组来保存i的值
    int rc;
    int i;
    for (i = 0; i < NUM_THREADS; i++) {
        cout << "main() : 创建线程, " << i << endl;
        indexes[i] = i; //先保存i的值
        // 传入的时候必须强制转换为void* 类型,即无类型指针
        rc = pthread_create(&threads[i], NULL, printHello, (void *) &(indexes[i]));
        if (rc) {
            cout << "Error:无法创建线程," << rc << endl;
            exit(-1);
        }
    }


    //等各个线程退出后,进程才结束,否则进程强制结束了,线程可能还没反应过来;
    pthread_exit(NULL);
}

向线程传递多参数

这个实例演示了如何通过结构传递多个参数。您可以在线程回调中传递任意的数据类型,因为它指向 void。

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

struct thread_data {
    int thread_id;
    char *message;
};

void *PrintHello(void *threadarg) {
    struct thread_data *my_data;

    my_data = (struct thread_data *) threadarg;

    cout << "Thread ID : " << my_data->thread_id;
    cout << " Message : " << my_data->message << endl;

    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    struct thread_data datas[NUM_THREADS];
    int rc;
    int i;

    for (i = 0; i < NUM_THREADS; i++) {
        cout << "main() : creating thread, " << i << endl;
        datas[i].thread_id = i;
        datas[i].message = (char *) "This is message";
        rc = pthread_create(&threads[i], NULL, PrintHello, (void *) &datas[i]);
        if (rc) {
            cout << "Error:unable to create thread," << rc << endl;
            exit(-1);
        }
    }
    pthread_exit(NULL);
}

连接和分离线程

ref:在Linux中使用线程

我们可以使用以下两个函数来连接或分离线程:

pthread_join (threadid, status) //阻塞当前线程的执行,直到threadid指定的线程完成了
pthread_detach (threadid) //将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源

pthread_join() 子程序阻碍调用程序,直到指定的 threadid 线程终止为止。当创建一个线程时,它的某个属性会定义它是否是可连接的(joinable)或可分离的(detached)。

joinable线程可在创建后,用pthread_detach()显式地分离。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的,则它永远也不能被连接。

这个实例演示了如何使用 pthread_join() 函数来等待线程的完成。

#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>

using namespace std;

#define NUM_THREADS     5

void *wait(void *t) {
    int i;
    long tid;

    tid = (long) t;

    sleep(1);
    cout << "Sleeping in thread " << endl;
    cout << "Thread with id : " << tid << "  ...exiting " << endl;
    pthread_exit(NULL);
}

int main() {
    int rc;
    int i;
    pthread_t threads[NUM_THREADS];
    /*typedef union pthread_attr_t pthread_attr_t;*/
    pthread_attr_t attr;
    void *status;

    // 初始化并设置线程为可连接的(joinable)
    /*extern int pthread_attr_init (pthread_attr_t *__attr) __THROW __nonnull ((1));*/
    pthread_attr_init(&attr);
    /*extern int pthread_attr_setdetachstate (pthread_attr_t *__attr,
                    int __detachstate)
     __THROW __nonnull ((1));*/
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

    for (i = 0; i < NUM_THREADS; i++) {
        cout << "main() : creating thread, " << i << endl;
        rc = pthread_create(&threads[i], NULL, wait, (void *) &i);
        if (rc) {
            cout << "Error:unable to create thread," << rc << endl;
            exit(-1);
        }
    }

    // 删除属性,并等待其他线程
    pthread_attr_destroy(&attr);
    for (i = 0; i < NUM_THREADS; i++) {
        /*The exit status of the thread is stored in *THREAD_RETURN (status), if THREAD_RETURN is not NULL.*/
        rc = pthread_join(threads[i], &status);
        if (rc) {
            cout << "Error:unable to join," << rc << endl;
            exit(-1);
        }
        cout << "Main: completed thread id :" << i;
        cout << "  exiting with status :" << status << endl;
    }

    cout << "Main: program exiting." << endl;
    pthread_exit(NULL);
}

join是三种同步线程的方式之一。另外两种分别是互斥锁(mutex)和条件变量(condition variable)。

C++ 11 之后标准的线程库

略,有兴趣看看例子

#include <iostream>

#include <thread>

std::thread::id main_thread_id = std::this_thread::get_id();

void hello() {
    std::cout << "Hello Concurrent World\n";
    if (main_thread_id == std::this_thread::get_id())
        std::cout << "This is the main thread.\n";
    else
        std::cout << "This is not the main thread.\n";
}

void pause_thread(int n) {
    std::this_thread::sleep_for(std::chrono::seconds(n));
    std::cout << "pause of " << n << " seconds ended\n";
}

int main() {
    std::thread t(hello);
    std::cout << t.hardware_concurrency() << std::endl;//可以并发执行多少个(不准确)
    std::cout << "native_handle " << t.native_handle() << std::endl;//可以并发执行多少个(不准确)
    t.join();
    std::thread a(hello);
    a.detach();
    std::thread threads[5];// 默认构造线程

    std::cout << "Spawning 5 threads...\n";
    for (int i = 0; i < 5; ++i)
        threads[i] = std::thread(pause_thread, i + 1);   // move-assign threads
    std::cout << "Done spawning threads. Now waiting for them to join:\n";
    for (auto &thread : threads)
        thread.join();
    std::cout << "All threads joined!\n";
}

线程的同步

互斥锁

获得资源的线程排斥其它没有获得资源的线程。

互斥锁在Linux中的名字是mutex,在系统底层机制叫futex。对于提供给用户使用的则是这个mutex。

Linux初始化和销毁互斥锁的接口是pthread_mutex_init()pthead_mutex_destroy(),对于加锁和解锁则有pthread_mutex_lock()、pthread_mutex_trylock()和pthread_mutex_unlock()。

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);  
int pthread_mutex_destory(pthread_mutex_t *mutex );  
int pthread_mutex_lock(pthread_mutex_t *mutex);  
int pthread_mutex_trylock(pthread_mutex_t *mutex);  
int pthread_mutex_unlock(pthread_mutex_t *mutex); 

条件变量

条件变量是一种事件机制。由一类线程来控制“事件”的发生,另外一类线程等待“事件”的发生。为了实现这种机制,条件变量必须是共享于线程之间的全局变量。而且,条件变量也需要与互斥锁同时使用。

初始化和销毁条件变量的接口是pthread_cond_init()和pthread_cond_destory();控制“事件”发生的接口是pthread_cond_signal()或pthread_cond_broadcast();等待“事件”发生的接口是pthead_cond_wait()或pthread_cond_timedwait()。

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);  
int pthread_cond_destory(pthread_cond_t *cond);  
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);  
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t *mutex, const timespec *abstime);  
int pthread_cond_signal(pthread_cond_t *cond);  
int pthread_cond_broadcast(pthread_cond_t *cond);  
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容