簡體   English   中英

如何在 C 中的結構中初始化 const(使用 malloc)

[英]How to initialize const in a struct in C (with malloc)

我試過了;

void *malloc(unsigned int);
struct deneme {
    const int a = 15;
    const int b = 16;
};

int main(int argc, const char *argv[])
{
    struct deneme *mydeneme = malloc(sizeof(struct deneme));
    return 0;
}

這是編譯器的錯誤:

gereksiz.c:3:17: error: expected ':', ',', ';', '}' or '__attribute__' before '=' token

而且,還有這個;

void *malloc(unsigned int);
struct deneme {
    const int a;
    const int b;
};

int main(int argc, const char *argv[])
{
    struct deneme *mydeneme = malloc(sizeof(struct deneme));
    mydeneme->a = 15;
    mydeneme->b = 20;
    return 0;
}

這是編譯器的錯誤:

gereksiz.c:10:5: error: assignment of read-only member 'a'
gereksiz.c:11:5: error: assignment of read-only member 'b'

兩者都沒有被編譯。 使用 malloc 分配內存時,有什么方法可以在結構內初始化 const 變量?

您需要拋棄 const 來初始化 malloc 結構的字段:

struct deneme *mydeneme = malloc(sizeof(struct deneme));
*(int *)&mydeneme->a = 15;
*(int *)&mydeneme->b = 20;

或者,您可以創建結構的初始化版本並對其進行 memcpy:

struct deneme deneme_init = { 15, 20 };
struct deneme *mydeneme = malloc(sizeof(struct deneme));
memcpy(mydeneme, &deneme_init, sizeof(struct deneme));

如果您deneme_init這樣做,您可以將deneme_init靜態和/或全局(因此它只需要構建一次)。


使用 C11 標准參考解釋為什么此代碼不是某些注釋所建議的未定義行為:

  • 此代碼不違反 6.7.3/6,因為malloc返回的空間不是“使用 const 限定類型定義的對象”。 表達式mydeneme->a不是一個對象,它是一個表達式。 雖然它有const限定類型,但它表示一個沒有用 const 限定類型定義的對象(實際上,根本沒有用任何類型定義)。

  • 寫入malloc分配的空間永遠不會違反嚴格的別名規則,因為每次寫入都會更新有效類型(6.5/6)。

(但是,從malloc分配的空間中讀取可能會違反嚴格的別名規則)。

在 Chris 的代碼示例中,第一個將整數值的有效類型設置為int ,第二個將有效類型設置為const int ,但是在這兩種情況下通過*mydeneme讀取這些值都是正確的,因為*mydeneme別名規則(6.5/7 項目符號 2)允許通過與對象的有效類型相同或更多限定的表達式來讀取對象。 由於表達式mydeneme->a類型為const int ,因此它可用於讀取有效類型intconst int

你有沒有試過這樣做:

int main(int argc, const char *argv[])
{
    struct deneme mydeneme = { 15, 20 };
    struct deneme *pmydeneme = malloc(sizeof(struct deneme));
    memcpy(pmydeneme, &mydeneme , sizeof(mydeneme));
    return 0;
}

我還沒有測試過,但代碼似乎是正確的

為了擴展@Chris Dodd 的答案,我一直在閱讀標准的“語言律師”詳細信息,看來這段代碼定義明確:

struct deneme deneme_init = { 15, 20 };
struct deneme *mydeneme = malloc(sizeof(struct deneme));
memcpy(mydeneme, &deneme_init, sizeof(struct deneme));

或者,要動態創建一個完整的 const 限定的結構對象:

const struct deneme deneme_init = { 15, 20 };
struct deneme *mydeneme = malloc(sizeof(struct deneme));
memcpy(mydeneme, &deneme_init, sizeof(struct deneme));

const struct deneme *read_only = mydeneme; 

理由:

在深入了解這一點時,首先需要確定的是,所謂的左值是否具有類型,如果有,該類型是否帶有限定符。 這在 C11 6.3.2.1/1 中定義:

左值是可能指定對象的表達式(具有除 void 之外的對象類型); 如果左值在評估時未指定對象,則行為未定義。 當一個對象被稱為具有特定類型時,該類型由用於指定該對象的左值指定。 可修改左值是沒有數組類型、沒有不完整類型、沒有常量限定類型的左值,如果它是結構或聯合,則沒有任何成員(遞歸地包括任何成員或元素)所有包含的聚合或聯合)具有 const 限定類型。

很明顯,左值不僅有類型,還有限定符。 如果它是 const 限定的或者如果它是具有 const 限定成員的結構,則它不是可修改的左值。

繼續討論“嚴格別名”和有效類型的規則,C11 6.5/7:

訪問其存儲值的對象的有效類型是該對象的聲明類型(如果有)。 87)如果一個值通過一個類型不是字符類型的左值存儲到一個沒有聲明類型的對象中,那么左值的類型成為該訪問和后續訪問的有效類型修改存儲的值。 如果使用memcpymemmove將值復制到沒有聲明類型的對象中,或者復制為字符類型的數組,則該訪問和不修改該值的后續訪問的修改對象的有效類型是從中復制值的對象的有效類型(如果有)。 對於沒有聲明類型的對象的所有其他訪問,對象的有效類型只是用於訪問的左值的類型。

  1. 分配的對象沒有聲明類型。

這意味着 malloc 返回的分配塊沒有有效類型,直到通過左值寫入訪問(通過賦值或memcpy某些內容存儲在該內存位置中。 然后它獲取該寫訪問中使用的左值的有效類型。

值得注意的是,指向該內存位置的指針的類型是完全無關的。 它也可能是一個volatile bananas_t*因為它不用於訪問左值(至少現在還沒有)。 只有用於左值訪問的類型才重要。

現在這就是它變得模糊的地方:是否通過可修改的左值完成此寫訪問可能很重要。 上面的有效類型規則沒有提到限定符,“嚴格別名規則”本身並不關心對象是否被限定(“類型”可以別名“限定類型”,反之亦然)。

但是在其他情況下,有效類型是否為只讀可能很重要:最值得注意的是,如果我們稍后嘗試對有效類型為 const 限定的對象進行非限定左值訪問。 (C11 6.7.3/6 “如果試圖通過使用具有非 const 限定類型的左值來修改以 const 限定類型定義的對象,則行為未定義。”)來自上面先前引用的部分左值,有效類型具有限定符是有意義的,即使標准沒有明確提及。

因此,為了絕對確定,我們必須獲得用於左值訪問權限的類型。 如果整個對象是只讀的,那么應該使用這篇文章頂部的第二個片段。 否則,如果它是讀/寫(但可能具有合格成員),則應使用第一個片段。 那么無論您如何閱讀標准,它都不會出錯。

有趣的是,我發現這種 C99 方式在 clang 中有效,但在 gcc 中無效

int main(int argc, const char *argv[])
{
    struct deneme *pmydeneme = malloc(sizeof(struct deneme));
    *pmydeneme = (struct deneme) {15, 20};
    return 0;
}

我不同意Christ Dodd的回答,因為我認為他的解決方案根據標准給出了Undefined Behavior ,正如其他人所說。

為了以不調用未定義行為的方式“解決” const限定符,我提出以下解決方案:

  1. 定義一個用malloc()調用初始化的void*變量。
  2. 定義所需類型的對象,在本例中為struct deneme並以const限定符不會抱怨的某種方式對其進行初始化(即,在聲明行本身中)。
  3. 使用memcpy()struct deneme對象的位復制到void*對象。
  4. 聲明一個指向struct deneme對象的指針並將其初始化為(void*)變量,之前已轉換為(struct deneme *)

所以,我的代碼是:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct deneme {
    const int a;
    const int b;
};
struct deneme* deneme_init(struct deneme data) {
    void *x = malloc(sizeof(struct deneme));
    memcpy(x, &data, sizeof(struct deneme));
    return (struct deneme*) x;
}
int main(void) {
    struct deneme *obj = deneme_init((struct deneme) { 15, 20, } );
    printf("obj->a: %d, obj->b: %d.\n", obj->a, obj->b);
    return 0;
}

標准使用const關鍵字作為左值限定符和存儲類之間的奇怪混合,但沒有明確哪個含義適用於結構成員。

如果一個結構體s類型為struct S ,成員mT類型,則構造s.foo接受一個struct S類型的左值並從中派生一個T類型的左值。 如果T包含限定符,則該修飾符將影響由此產生的左值。

標准當然承認代碼可能采用非const限定的左值,從該左值派生出一個具有const限定的左值,從那個 - 就像原始 - 不是的左值派生,然后使用后者左值來修改對象。 不清楚的是,在結構成員上使用const修飾符是否會影響對象的底層存儲類,或者它是否只會導致將const修飾符應用於使用成員訪問運算符形成的任何左值。 我認為后一種解釋更有意義,因為前者會導致許多模棱兩可且不可行的極端情況,但我認為該標准沒有明確說明應該適用哪種解釋。 由於在前一種解釋下定義其行為的所有情況在后一種解釋下都將被相同定義,我認為標准的作者沒有理由不認為后一種解釋更優越,但他們可能希望留下這種可能性在某些情況下,在某些實施中,前一種解釋可能會提供委員會沒有預見到的一些有利實施。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM