欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

深入剖析Android中init進(jìn)程實(shí)現(xiàn)的C語(yǔ)言源碼

 更新時(shí)間:2015年07月30日 15:10:59   作者:低調(diào)小一  
這篇文章主要介紹了Android中init進(jìn)程實(shí)現(xiàn)的C語(yǔ)言源碼,init屬性服務(wù)在安卓中屬于系統(tǒng)的底層Linux服務(wù),需要的朋友可以參考下

概述

init是一個(gè)進(jìn)程,確切的說(shuō),它是Linux系統(tǒng)中用戶(hù)空間的第一個(gè)進(jìn)程。由于Android是基于Linux內(nèi)核的,所以init也是Android系統(tǒng)中用戶(hù)空間的第一個(gè)進(jìn)程。init的進(jìn)程號(hào)是1。作為天字第一號(hào)進(jìn)程,init有很多重要的工作:

  •     init提供property service(屬性服務(wù))來(lái)管理Android系統(tǒng)的屬性。
  •     init負(fù)責(zé)創(chuàng)建系統(tǒng)中的關(guān)鍵進(jìn)程,包括zygote。

以往的文章一上來(lái)就介紹init的源碼,但是我這里先從這兩個(gè)主要工作開(kāi)始。搞清楚這兩個(gè)主要工作是如何實(shí)現(xiàn)的,我們?cè)倩仡^來(lái)看init的源碼。

這篇文章主要是介紹init進(jìn)程的屬性服務(wù)。

    跟init屬性服務(wù)相關(guān)的源碼目錄如下:

    system/core/init/
    bionic/libc/bionic/
    system/core/libcutils/

屬性服務(wù)

在windows平臺(tái)上有一個(gè)叫做注冊(cè)表的東西,它可以存儲(chǔ)一些類(lèi)似key/value的鍵值對(duì)。一般而言,系統(tǒng)或者某些應(yīng)用程序會(huì)把自己的一些屬性存儲(chǔ)在注冊(cè)表中,即使系統(tǒng)重啟或應(yīng)用程序重啟,它還能根據(jù)之前在注冊(cè)表中設(shè)置的屬性值,進(jìn)行相應(yīng)的初始化工作。

Android系統(tǒng)也提供了類(lèi)似的機(jī)制,稱(chēng)之為屬性服務(wù)(property service)。應(yīng)用程序可以通過(guò)這個(gè)服務(wù)查詢(xún)或者設(shè)置屬性。我們可以通過(guò)如下命令,獲取手機(jī)中屬性鍵值對(duì)。

adb shell getprop

例如紅米Note手機(jī)的屬性值如下:

[ro.product.device]: [lcsh92_wet_jb9]
[ro.product.locale.language]: [zh]
[ro.product.locale.region]: [CN]
[ro.product.manufacturer]: [Xiaomi]


在system/core/init/init.c文件的main函數(shù)中,跟屬性服務(wù)的相關(guān)代碼如下:

property_init();
queue_builtin_action(property_service_init_action, "property_service_init");

接下來(lái),我們分別看一下這兩處代碼的具體實(shí)現(xiàn)。
屬性服務(wù)初始化
創(chuàng)建存儲(chǔ)空間

首先,我們先來(lái)看一下property_init函數(shù)的源碼(/system/core/init/property_service.c):

void property_init(void)
{
  init_property_area();
}


property_init函數(shù)中只是簡(jiǎn)單的調(diào)用了init_property_area方法,接下來(lái)我們看一下這個(gè)方法的具體實(shí)現(xiàn):

static int property_area_inited = 0;
static workspace pa_workspace;
static int init_property_area(void)
{
  // 屬性空間是否已經(jīng)初始化
  if (property_area_inited)
    return -1;

  if (__system_property_area_init())
    return -1;

  if (init_workspace(&pa_workspace, 0))
    return -1;

  fcntl(pa_workspace.fd, F_SETFD, FD_CLOEXEC);

  property_area_inited = 1;
  return 0;
}

從init_property_area函數(shù),我們可以看出,函數(shù)首先判斷屬性?xún)?nèi)存區(qū)域是否已經(jīng)初始化過(guò),如果已經(jīng)初始化,則返回-1。如果沒(méi)有初始化,我們接下來(lái)會(huì)發(fā)現(xiàn)有兩個(gè)關(guān)鍵函數(shù)__system_property_area_init和init_workspace應(yīng)該是跟內(nèi)存區(qū)域初始化相關(guān)。那我們分別分析一下這兩個(gè)函數(shù)具體實(shí)現(xiàn)。

__system_property_area_init

__system_property_area_init函數(shù)位于/bionic/libc/bionic/system_properties.c文件中,具體代碼實(shí)現(xiàn)如下:

struct prop_area {
  unsigned bytes_used;
  unsigned volatile serial;
  unsigned magic;
  unsigned version;
  unsigned reserved[28];
  char data[0];
};
typedef struct prop_area prop_area;
prop_area *__system_property_area__ = NULL;

#define PROP_FILENAME "/dev/__properties__"
static char property_filename[PATH_MAX] = PROP_FILENAME; 

#define PA_SIZE (128 * 1024)


static int map_prop_area_rw()
{
  prop_area *pa;
  int fd;
  int ret;

  /**
   * O_RDWR ==> 讀寫(xiě)
   * O_CREAT ==> 若不存在,則創(chuàng)建
   * O_NOFOLLOW ==> 如果filename是軟鏈接,則打開(kāi)失敗
   * O_EXCL ==> 如果使用O_CREAT是文件存在,則可返回錯(cuò)誤信息
   */
  fd = open(property_filename, O_RDWR | O_CREAT | O_NOFOLLOW | O_CLOEXEC | O_EXCL, 0444);
  if (fd < 0) {
    if (errno == EACCES) {
      abort();
    }
    return -1;
  }

  ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
  if (ret < 0)
    goto out;

  if (ftruncate(fd, PA_SIZE) < 0)
    goto out;

  pa_size = PA_SIZE;
  pa_data_size = pa_size - sizeof(prop_area);
  compat_mode = false;

  // mmap映射文件實(shí)現(xiàn)共享內(nèi)存
  pa = mmap(NULL, pa_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  if (pa == MAP_FAILED)
    goto out;

  /*初始化內(nèi)存地址中所有值為0*/
  memset(pa, 0, pa_size);
  pa->magic = PROP_AREA_MAGIC;
  pa->version = PROP_AREA_VERSION;
  pa->bytes_used = sizeof(prop_bt);

  __system_property_area__ = pa;

  close(fd);
  return 0;

out:
  close(fd);
  return -1;
}

int __system_property_area_init()
{
  return map_prop_area_rw();
}

 
代碼比較好理解,主要內(nèi)容是利用mmap映射property_filename創(chuàng)建了一個(gè)共享內(nèi)存區(qū)域,并將共享內(nèi)存的首地址賦值給全局變量__system_property_area__。

關(guān)于mmap映射文件實(shí)現(xiàn)共享內(nèi)存IPC通信機(jī)制,可以參考這篇文章:mmap實(shí)現(xiàn)IPC通信機(jī)制
init_workspace

接下來(lái),我們來(lái)看一下init_workspace函數(shù)的源碼(/system/core/init/property_service.c):

typedef struct {
  void *data;
  size_t size;
  int fd;
}workspace;

static int init_workspace(workspace *w, size_t size)
{
  void *data;
  int fd = open(PROP_FILENAME, O_RDONLY | O_NOFOLLOW);
  if (fd < 0)
    return -1;

  w->size = size;
  w->fd = fd;
  return 0;
}

客戶(hù)端進(jìn)程訪問(wèn)屬性?xún)?nèi)存區(qū)域

雖然屬性?xún)?nèi)存區(qū)域是init進(jìn)程創(chuàng)建的,但是Android系統(tǒng)希望其他進(jìn)程也能夠讀取這塊內(nèi)存區(qū)域里的內(nèi)容。為了做到這一點(diǎn),init進(jìn)程在屬性區(qū)域初始化過(guò)程中做了如下兩項(xiàng)工作:

    把屬性?xún)?nèi)存區(qū)域創(chuàng)建在共享內(nèi)存上,而共享內(nèi)存是可以跨進(jìn)程的。這一點(diǎn),在上述代碼中是通過(guò)mmap映射/dev/__properties__文件實(shí)現(xiàn)的。pa_workspace變量中的fd成員也保存了映射文件的句柄。
    如何讓其他進(jìn)程知道這個(gè)共享內(nèi)存句柄呢?Android先將文件映射句柄賦值給__system_property_area__變量,這個(gè)變量屬于bionic_lic庫(kù)中的輸出的一個(gè)變量,然后利用了gcc的constructor屬性,這個(gè)屬性指明了一個(gè)__lib_prenit函數(shù),當(dāng)bionic_lic庫(kù)被加載時(shí),將自動(dòng)調(diào)用__libc_prenit,這個(gè)函數(shù)內(nèi)部完成共享內(nèi)存到本地進(jìn)程的映射工作。

只講原理是不行的,我們直接來(lái)看一下__lib_prenit函數(shù)代碼的相關(guān)實(shí)現(xiàn):

void __attribute__((constructor)) __libc_prenit(void);
void __libc_prenit(void)
{
  // ...
  __libc_init_common(elfdata); // 調(diào)用這個(gè)函數(shù)
  // ...
}


__libc_init_common函數(shù)為:

void __libc_init_common(uintptr_t *elfdata)
{
  // ...
  __system_properties_init(); // 初始化客戶(hù)端的屬性存儲(chǔ)區(qū)域
}

 
__system_properties_init函數(shù)有回到了我們熟悉的/bionic/libc/bionic/system_properties.c文件:

static int get_fd_from_env(void)
{
  char *env = getenv("ANDROID_PROPERTY_WORKSPACE");

  if (! env) {
    return -1;
  }

  return atoi(env);
}

static int map_prop_area()
{
  bool formFile = true;
  int result = -1;
  int fd;
  int ret;

  fd = open(property_filename, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
  if (fd >= 0) {
    /* For old kernels that don't support O_CLOEXEC */
    ret = fcntl(fd, F_SETFD, FD_CLOEXEC);
    if (ret < 0)
      goto cleanup;
  }

  if ((fd < 0) && (error == ENOENT)) {
    fd = get_fd_from_env();
    fromFile = false;
  }

  if (fd < 0) {
    return -1;
  }

  struct stat fd_stat;
  if (fstat(fd, &fd_stat) < 0) {
    goto cleanup;
  }

  if ((fd_stat.st_uid != 0)
      || (fd_stat.st_gid != 0)
      || (fd_stat.st_mode & (S_IWGRP | S_IWOTH) != 0)
      || (fd_stat.st_size < sizeof(prop_area))) {
    goto cleanup;
  }

  pa_size = fd_stat.st_size;
  pa_data_size = pa_size - sizeof(prop_area);

  /* 
   * 映射init創(chuàng)建的屬性?xún)?nèi)存到本地進(jìn)程空間,這樣本地進(jìn)程就可以使用這塊共享內(nèi)存了。
   * 注意:映射時(shí)制定了PROT_READ屬性,所以客戶(hù)端進(jìn)程只能讀屬性,不能設(shè)置屬性。
   */
  prop_area *pa = mmap(NULL, pa_size, PROT_READ, MAP_SHARED, fd, 0);

  if (pa == MAP_FAILED) {
    goto cleanup;
  }

  if ((pa->magic != PROP_AREA_MAGIC) || (pa->version != PROP_AREA_VERSION && pa->version != PROP_AREA_VERSION_COMPAT)) {
    munmap(pa, pa_size);
    goto cleanup;
  }

  if (pa->version == PROP_AREA_VERSION_COMPAT) {
    compat_mode = true;
  }

  result = 0;

  __system_property_area__ = pa;
cleanup:
  if (fromFile) {
    close(fd);
  }

  return result;
}

int __system_properties_init()
{
  return map_prop_area();
}


通過(guò)對(duì)源碼的閱讀,可以發(fā)現(xiàn),客戶(hù)端通過(guò)mmap映射,可以讀取屬性?xún)?nèi)存的內(nèi)容,但是沒(méi)有權(quán)限設(shè)置屬性。那客戶(hù)端是如何設(shè)置屬性的呢?這就涉及到下面要將的屬性服務(wù)器了。
屬性服務(wù)器的分析

init進(jìn)程會(huì)啟動(dòng)一個(gè)屬性服務(wù)器,而客戶(hù)端只能通過(guò)與屬性服務(wù)器的交互來(lái)設(shè)置屬性。
啟動(dòng)屬性服務(wù)器

先來(lái)看一下屬性服務(wù)器的內(nèi)容,它由property_service_init_action函數(shù)啟動(dòng),源碼如下(/system/core/init/init.c&&property_service.c):

static int property_service_init_action(int nargs, char **args)
{
  start_property_service();
  return 0;
}

static void load_override_properties()
{
#ifdef ALLOW_LOCAL_PROP_OVERRIDE
  char debuggable[PROP_VALUE_MAX];
  int ret;

  ret = property_get("ro.debuggable", debuggable);
  if (ret && (strcmp(debuggable, "1") == 0)) {
    load_properties_from_file(PROP_PATH_LOCAL_OVERRIDE);
  }
#endif
}

static void load_properties(char *data)
{
  char *key, *value, *eol, *sol, *tmp;

  sol = data;
  while ((eol = strchr(sol, '\n'))) {
    key = sol;
    // 賦值下一行的指針給sol
    *eol ++ = 0;
    sol = eol;

    value = strchr(key, '=');
    if (value == 0) continue;
    *value++ = 0;

    while (isspace(*key)) key ++;
    if (*key == '#') continue;
    tmp = value - 2;
    while ((tmp > key) && isspace(*tmp)) *tmp-- = 0;

    while (isspace(*value)) value ++;
    tmp = eol - 2;
    while ((tmp > value) && isspace(*tmp)) *tmp-- = 0;

    property_set(key, value);
  }
}

int create_socket(const char *name, int type, mode_t perm, uid_t uid, gid_t gid)
{
  struct sockaddr_un addr;
  int fd, ret;
  char *secon;

  fd = socket(PF_UNIX, type, 0);
  if (fd < 0) {
    ERROR("Failed to open socket '%s': %s\n", name, strerror(errno));
    return -1;
  }

  memset(&addr, 0, sizeof(addr));
  addr.sun_family = AF_UNIX;
  snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s", name);

  ret = unlink(addr.sun_path);
  if (ret != 0 && errno != ENOENT) {
    goto out_close;
  }

  ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
  if (ret) {
    goto out_unlink;
  }
  chown(addr.sun_path, uid, gid);
  chmod(addr.sun_path, perm);

  return fd;

out_unlink:
  unlink(addr.sun_path);
out_close:
  close(fd);
  return -1;
}

#define PROP_PATH_SYSTEM_BUILD "/system/build.prop"
#define PROP_PATH_SYSTEM_DEFAULT "/system/default.prop"
#define PROP_PATH_LOCAL_OVERRIDE "/data/local.prop"
#define PROP_PATH_FACTORY "/factory/factory.prop"

void start_property_service(void)
{
  int fd;

  load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
  load_properties_from_file(PROP_PATH_SYSTEM_DEFAULT);
  load_override_properties();
  /*Read persistent properties after all default values have been loaded.*/
  load_persistent_properties();

  fd = create_socket(PROP_SERVICE_NAME, SOCK_STREAM, 0666, 0, 0);
  if (fd < 0) return;
  fcntl(fd, F_SETFD, FD_CLOEXEC);
  fcntl(fd, F_SETFL, O_NONBLOCK);

  listen(fd, 8);
  property_set_fd = fd;
}


從上述代碼可以看到,init進(jìn)程除了會(huì)預(yù)寫(xiě)入指定文件(例如:system/build.prop)屬性外,還會(huì)創(chuàng)建一個(gè)UNIX Domain Socket,用于接受客戶(hù)端的請(qǐng)求,構(gòu)建屬性。那這個(gè)socket請(qǐng)求是再哪里被處理的呢?
答案是:在init中的for循環(huán)處已經(jīng)進(jìn)行了相關(guān)處理。

服務(wù)端處理設(shè)置屬性請(qǐng)求

接收屬性設(shè)置請(qǐng)求的地方是在init進(jìn)程中,相關(guān)代碼如下所示:

int main(int argc, char **argv)
{
  // ...省略不相關(guān)代碼

  for (;;) {
    // ...
    for (i = 0; i < fd_count; i ++) {
      if (ufds[i].fd == get_property_set_fd())
        handle_property_set_fd();
    }
  }
}

從上述代碼可以看出,當(dāng)屬性服務(wù)器收到客戶(hù)端請(qǐng)求時(shí),init進(jìn)程會(huì)調(diào)用handle_property_set_fd函數(shù)進(jìn)行處理,函數(shù)位置是:system/core/init/property_service.c,我們來(lái)看一下這個(gè)函數(shù)的實(shí)現(xiàn)源碼:

void handle_property_set_fd()
{
  prop_msg msg;
  int s;
  int r;
  int res;
  struct ucred cr;
  struct sockaddr_un addr;
  socklen_t addr_size = sizeof(addr);
  socklen_t cr_size = sizeof(cr);
  char *source_ctx = NULL;

  // 接收TCP連接
  if ((s = accept(property_set_fd, (struct sockaddr *) &addr, &addr_size)) < 0) {
    return;
  }

  // 接收客戶(hù)端請(qǐng)求數(shù)據(jù)
  r = TEMP_FAILURE_RETRY(recv(s, &msg, sizeof(msg), 0));
  if (r != sizeof(prop_msg)) {
    ERROR("sys_prop: mis-match msg size received: %d expected : %d errno: %d\n", r, sizeof(prop_msg), errno);
    close(s);
    return;
  }

  switch(msg.cmd) {
  case PROP_MSG_SETPROP:
    msg.name[PROP_NAME_MAX - 1] = 0;
    msg.value[PROP_VALUE_MAX - 1] = 0;

    if (memcmp(msg.name, "ctl.", 4) == 0) {
      close(s);
      if (check_control_perms(msg.value, cr.uid, cr.gid, source_ctx)) {
        handle_control_message((char*) msg.name + 4, (char*) msg.value);
      } else {
        ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n", msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);
      }
    } else {
      if (check_perms(msg.name, cr.uid, cr.gid, source_ctx)) {
        property_set((char *) msg.name, (char*) msg.value);
      }
      close(s);
    }
    break;
  default:
    close(s);
    break;
  }
}


當(dāng)客戶(hù)端的權(quán)限滿(mǎn)足要求時(shí),init就調(diào)用property_set進(jìn)行相關(guān)處理。property_set源碼實(shí)現(xiàn)如下:

int property_set(const char *name, const char *value)
{
  prop_info *pi;
  int ret;

  size_t namelen = strlen(name);
  size_t valuelen = strlen(value);

  if (! is_legal_property_name(name, namelen)) return -1;
  if (valuelen >= PROP_VALUE_MAX) return -1;

  // 從屬性空間中尋找是否已經(jīng)存在該屬性值
  pi = (prop_info*) __system_property_find(name);
  if (pi != 0) {
    // ro開(kāi)頭的屬性被設(shè)置后,不允許再被修改
    if (! strncmp(name, "ro.", 3)) return -1;

    __system_property_update(pi, value, valuelen);
  } else {
    ret = __system_property_add(name, namelen, value, valuelen);
  }

  // 有一些特殊的屬性需要特殊處理,例如net.和persist.開(kāi)頭的屬性
  if (strncmp("net.", name, strlen("net.")) == 0) {
    if (strcmp("net.change", name) == 0) {
      return 0;
    }
    property_set("net.change", name);
  } else if (persistent_properties_loaded && strncmp("persist.", name, strlen("persist.")) == 0) {
    write_persistent_property(name, value);
  }
  property_changed(name, value);
  return 0;
}


屬性服務(wù)器端的工作基本到這里就完成了。最后,我們來(lái)看一下客戶(hù)端是如何發(fā)送設(shè)置屬性的socket請(qǐng)求。
客戶(hù)端發(fā)送請(qǐng)求

客戶(hù)端設(shè)置屬性時(shí)是調(diào)用了property_set(“sys.istest”, “true”)方法。從上述分析可知,該方法實(shí)現(xiàn)跟服務(wù)器端的property_set方法不同,該方法一定是發(fā)送了socket請(qǐng)求,該方法源碼位置為:/system/core/libcutils/properties.c:

int property_set(const char *key, const char *value)
{
  return __system_property_set(key, value);
}

可以看到,property_set調(diào)用了__system_property_set方法,這個(gè)方法位于:/bionic/libc/bionic/system_properties.c文件中:

struct prop_msg
{
  unsigned cmd;
  char name[PROP_NAME_MAX];
  char value[PROP_VALUE_MAX];
};
typedef struct prop_msg prop_msg;

static int send_prop_msg(prop_msg *msg)
{
  struct pollfd pollfds[1];
  struct sockaddr_un addr;
  socklen_t alen;
  size_t namelen;
  int s;
  int r;
  int result = -1;

  s = socket(AF_LOCAL, SOCK_STREAM, 0);
  if (s < 0) {
    return result;
  }

  memset(&addr, 0, sizeof(addr));
  namelen = strlen(property_service_socket);
  strlcpy(addr.sun_path, property_service_socket, sizeof(addr.sun_path));
  addr.sun_family = AF_LOCAL;
  alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;

  if (TEMP_FAILURE_RETRY(connect(s, (struct sockaddr *) &addr, alen)) < 0) {
    close(s);
    return result;
  }

  r = TEMP_FAILURE_RETRY(send(s, msg, sizeof(prop_msg), 0));

  close(s);
  return result;
}

int __system_property_set(const char *key, const char *value)
{
  int err;
  prop_msg msg;

  if (key == 0) return -1;
  if (value == 0) value = "";
  if (strlen(key) >= PROP_NAME_MAX) return -1;
  if (strlen(value) >= PROP_VALUE_MAX) return -1;

  memset(&msg, 0, sizeof(msg));
  msg.cmd = PROP_MSG_SETPROP;
  strlcpy(msg.name, key, sizeof(msg.name));
  strlcpy(msg.value, value, sizeof(msg.value));

  err = send_prop_msg(&msg);
  if (err < 0) {
    return err;
  }
  return 0;
}

相關(guān)文章

  • C++中平衡二叉搜索樹(shù)的模擬實(shí)現(xiàn)

    C++中平衡二叉搜索樹(shù)的模擬實(shí)現(xiàn)

    二叉搜索樹(shù)雖可以縮短查找的效率,但如果數(shù)據(jù)有序或接近有序二叉搜索樹(shù)將退化為單支樹(shù),查找元素相當(dāng)于在順序表中搜索元素,效率低下,所以本文給大家介紹了C++平衡二叉的搜索樹(shù)模擬實(shí)現(xiàn)方法,需要的朋友可以參考下
    2023-09-09
  • C++ 自定義棧實(shí)現(xiàn)迷宮求解

    C++ 自定義棧實(shí)現(xiàn)迷宮求解

    這篇文章主要介紹了C++ 自定義棧實(shí)現(xiàn)迷宮求解的相關(guān)資料,需要的朋友可以參考下
    2017-07-07
  • C語(yǔ)言模擬實(shí)現(xiàn)密碼輸入的示例代碼

    C語(yǔ)言模擬實(shí)現(xiàn)密碼輸入的示例代碼

    本文主要介紹了C語(yǔ)言模擬實(shí)現(xiàn)密碼輸入的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • 用C語(yǔ)言求解一元二次方程的簡(jiǎn)單實(shí)現(xiàn)

    用C語(yǔ)言求解一元二次方程的簡(jiǎn)單實(shí)現(xiàn)

    這篇文章主要介紹了用C語(yǔ)言求解一元二次方程的簡(jiǎn)單實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • C語(yǔ)言制作掃雷游戲(圖形庫(kù))

    C語(yǔ)言制作掃雷游戲(圖形庫(kù))

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言制作掃雷游戲,結(jié)合圖形庫(kù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-06-06
  • C++實(shí)現(xiàn)一維向量旋轉(zhuǎn)算法

    C++實(shí)現(xiàn)一維向量旋轉(zhuǎn)算法

    這篇文章主要介紹了C++實(shí)現(xiàn)一維向量旋轉(zhuǎn)算法,非常實(shí)用的經(jīng)典算法,需要的朋友可以參考下
    2014-08-08
  • 6個(gè)變態(tài)的C語(yǔ)言Hello World程序

    6個(gè)變態(tài)的C語(yǔ)言Hello World程序

    這篇文章主要介紹了6個(gè)變態(tài)的C語(yǔ)言Hello World程序,需要的朋友可以參考下
    2016-05-05
  • C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單餐飲管理與點(diǎn)餐系統(tǒng)

    C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單餐飲管理與點(diǎn)餐系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單餐飲管理與點(diǎn)餐系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C++內(nèi)存管理介紹

    C++內(nèi)存管理介紹

    這篇文章主要介紹了C++內(nèi)存管理,C++標(biāo)準(zhǔn)委員會(huì)給我們提供了auto_ptr智能指針,后面又引入了share_ptr以及weak_ptr幫助我們正確和安全的使用指針,本文主要是介紹boost庫(kù)提供的解決方案,需要的朋友可以參考一下
    2022-01-01
  • 你知道C語(yǔ)言中#和##表示的意義嗎

    你知道C語(yǔ)言中#和##表示的意義嗎

    如標(biāo)題,這篇文章會(huì)講解C語(yǔ)言中的#和##是啥意思。我相信,大部分朋友應(yīng)該都沒(méi)怎么用過(guò),這兩個(gè)玩意的使用條件也相當(dāng)苛刻,快跟隨小編一起來(lái)看看吧
    2023-04-04

最新評(píng)論